---
title: Dynamic Panel with Repeating Group of Fields
product: Form Library
description: With SurveyJS, you can set up dynamic forms with repeating groups of fields. Enable respondents to effortlessly add fresh copies of the same fields on the fly to list multiple instances of education, places of work, and more. Check our this free JavaScript demo showcasing the duplicate group option to learn more.
framework: Angular
source: https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/angular
index: https://surveyjs.io/form-library/examples/overview.md
---

# Dynamic Panel with Repeating Group of Fields (Angular)

If you want to set up a form with a group of repeating fields, for example, to list respondents' qualifications or places of work, use a Dynamic Panel element. It enables respondents to add a fresh copy of the same form fields on the fly.

## Create a Dynamic Panel

To add a Dynamic Panel to your survey or form, define an object with the `type` property set to `"paneldynamic"` and add it to the [`elements`](https://surveyjs.io/form-library/documentation/pagemodel#elements) array. Within this object, specify Dynamic Panel's [`title`](https://surveyjs.io/form-library/documentation/api-reference/dynamic-panel-model#title) and a unique [`name`](https://surveyjs.io/form-library/documentation/api-reference/dynamic-panel-model#name) that identifies the panel. Optionally, you can specify a [`description`](https://surveyjs.io/form-library/documentation/api-reference/dynamic-panel-model#description) to place under the `title`.

## Configure Form Fields to Duplicate

A Dynamic Panel contains entries with duplicated content. This content is based on a template that you can configure using the [`templateElements`](/form-library/documentation/api-reference/dynamic-panel-model#templateElements) array. Populate this array with configuration objects of survey questions and panels as shown in this demo.

Panel entries also can display titles and descriptions based on templates. Use the [`templateTitle`](/form-library/documentation/api-reference/dynamic-panel-model#templateTitle) and [`templateDescription`](/form-library/documentation/api-reference/dynamic-panel-model#templateDescription) properties to specify these templates.

If you need to reference an entry index in dynamic texts (titles, descriptions, etc.), use the `{panelIndex}` and `{visiblePanelIndex}` placeholders. They contain panel indices within the collections of all or only visible panels, respectively. These indices start with 1.

## Access Nested Questions

When you implement an expression or configure dynamic text, you may need to access questions within the same panel entry. To do this, use the `panel` prefix: `{panel.my-question}`. Refer to any expression in this demo for an example.

If you want to access an entry question from outside the panel entry, use the entry's index: `{my-dynamic-panel[0].my-question}`.

## Add and Delete Panel Entries

Respondents can add and delete panel entries at runtime. If respondents should confirm deletion, enable the [`confirmDelete`](/form-library/documentation/api-reference/dynamic-panel-model#confirmDelete) property. If you want to disallow respondents to add and delete entries, disable the [`allowAddPanel`](/form-library/documentation/api-reference/dynamic-panel-model#allowAddPanel) and [`allowRemovePanel`](/form-library/documentation/api-reference/dynamic-panel-model#allowRemovePanel) properties.

A Dynamic Panel also allows you to add or delete an entry by calling the [`addPanel()`](/form-library/documentation/api-reference/dynamic-panel-model#addPanel) and [`removePanel()`](/form-library/documentation/api-reference/dynamic-panel-model#removePanel) methods. `removePanel()` accepts an entry index or [`PanelModel`](/form-library/documentation/api-reference/panel-model) instance as an argument. Before calling these methods, use `SurveyModel`'s [`getQuestionByName(name)`](/form-library/documentation/api-reference/survey-data-model#getQuestionByName) method to access a Dynamic Panel:

```js
import { Model } from "survey-core";

const survey = new Model(json);
const panel = survey.getQuestionByName("my-dynamic-panel");
```

You can also add one or more identical entries at startup. To do this, assign a number to the [`panelCount`](/form-library/documentation/api-reference/dynamic-panel-model#panelCount) property.

## Limit the Number of Entries

If you want to specify the minimum or maximum number of panel entries users can add, set the [`minPanelCount`](/form-library/documentation/api-reference/dynamic-panel-model#minPanelCount) and [`maxPanelCount`](/form-library/documentation/api-reference/dynamic-panel-model#maxPanelCount) properties. On reaching the limit, Dynamic Panel automatically hides buttons that add or delete entries. In this demo, a Dynamic Panel disallows you to add more than 10 entries.

## Files

### `src/app/components/json.ts`

```ts
export const json = {
  "elements": [
    {
      "type": "paneldynamic",
      "name": "vacation-info",
      "title": "Enter information about your vacations",
      "panelCount": 1,
      "maxPanelCount": 10,
      "confirmDelete": true,
      "templateElements": [
        {
          "type": "dropdown",
          "name": "country",
          "title": "Select a country",
          "choicesByUrl": {
            "url": "https://surveyjs.io/api/CountriesExample",
            "valueName": "name"
          }
        },
        {
          "type": "text",
          "name": "start-date",
          "title": "Select a vacation start date",
          "defaultValueExpression": "today()",
          "inputType": "date",
          "visibleIf": "{panel.country} notempty"
        },
        {
          "type": "text",
          "name": "end-date",
          "startWithNewLine": false,
          "title": "Select a vacation end date",
          "defaultValueExpression": "today(10)",
          "validators": [
            {
              "type": "expression",
              "text": "End date should be greater than start date",
              "expression": "{panel.start-date} < {panel.end-date}"
            }
          ],
          "inputType": "date",
          "visibleIf": "{panel.country} notempty"
        },
        {
          "type": "html",
          "name": "days-spent",
          "readOnly": true,
          "html": "<b>Days spent in {panel.country}</b>: {panel.diffdays}",
          "titleLocation": "left",
          "visibleIf": "{panel.country} notempty and {panel.start-date} notempty and {panel.end-date} notempty and {panel.start-date} < {panel.end-date}"
        },
        {
          "type": "expression",
          "name": "diffdays",
          "expression": "iif({panel.start-date} < {panel.end-date}, diffDays({panel.start-date}, {panel.end-date}), 0)",
          "visible": false
        }
      ]
    }
  ]
};
```

### `src/app/components/survey.component.css`

```css
/* You can add your custom CSS here. */
```

### `src/app/components/survey.component.html`

```html
<survey [model]="model" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; height:100%"></survey>
```

### `src/app/components/survey.component.ts`

```ts
import { Component, OnInit } from "@angular/core";
import { Model } from "survey-core";
import { json } from "./json";
import "./survey.component.css";
import "survey-core/survey-core.min.css";

@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey",
    templateUrl: "./survey.component.html",
    styleUrls: ["./survey.component.css"]
})
export class SurveyComponent implements OnInit {
    model: Model;
    ngOnInit() {
        const survey = new Model(json);
        survey.onComplete.add((sender, options) => {
            console.log(JSON.stringify(sender.data, null, 3));
        });
        this.model = survey;
    }
}
```

### `src/app/components/theme.ts`

```ts
export const themeJson = {};
```

### `src/app/app.component.css`

```css

```

### `src/app/app.component.html`

```html
<component-survey></component-survey>
```

### `src/app/app.component.ts`

```ts
import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"]
})
export class AppComponent {
    title = "CodeSandbox";
}
```

### `src/app/app.module.ts`

```ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppComponent } from "./app.component";
import { SurveyModule } from "survey-angular-ui";
import { SurveyComponent } from "./components/survey.component";

@NgModule({
    declarations: [AppComponent, SurveyComponent],
    imports: [BrowserModule, SurveyModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

### `src/environments/environment.prod.ts`

```ts
export const environment = {
    production: true
};
```

### `src/environments/environment.ts`

```ts
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
    production: false
};
```

### `src/index.html`

```html
<app-root></app-root>
```

### `src/main.ts`

```ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
```

### `src/polyfills.ts`

```ts
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import "core-js/proposals/reflect-metadata";

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install web-animations-js`.

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import "zone.js/dist/zone"; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
```

### `src/styles.css`

```css
/* You can add your custom CSS here. */
```

### `src/typings.d.ts`

```ts
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
    id: string;
}
```

### `.angular-cli.json`

```json
{
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [ "assets", "favicon.ico" ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "prefix": "app",
      "styles": [ "styles.css" ],
      "scripts": [ ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ]
}
```

### `_tsconfig.json`

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "resolveJsonModule": true,
    "target": "es2015",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```

### `package.json`

```json
{
  "name": "surveyjs-angular",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "14.1.1",
    "@angular/cdk": "14.1.1",
    "@angular/common": "14.1.1",
    "@angular/compiler": "14.1.1",
    "@angular/core": "14.1.1",
    "@angular/forms": "14.1.1",
    "@angular/platform-browser": "14.1.1",
    "@angular/platform-browser-dynamic": "14.1.1",
    "@angular/router": "14.1.1",
    "core-js": "3.6.4",
    "rxjs": "6.5.4",
    "survey-core": "latest",
    "survey-angular-ui": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular/cli": "1.6.6",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/core-js": "0.9.46",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "3.9.7"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/reactjs.md)
- [Vue 3](https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/vue3js.md)
- [jQuery](https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/jquery.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/duplicate-group-of-fields-in-form/vanillajs.md)
