---
title: Breakable Loop
product: Form Library
description: Allow your respondents to duplicate a group of questions as many times as needed, for example, to add multiple addresses, contacts, family members, or other entries. Set a maximum number of groups to stop the repetition automatically. View the JavaScript demo to see the breakable loop feature in action.
framework: Angular
source: https://surveyjs.io/form-library/examples/breakable-loop/angular
index: https://surveyjs.io/form-library/examples/overview.md
---

# Breakable Loop (Angular)

A breakable loop allows respondents to repeat the same set of questions in a loop to add multiple entries. Respondents can exit the loop when they want to, or the loop can end automatically when a certain condition is met. This may happen when a maximum number of entries is reached, a specific value is entered, or a validation rule is triggered. Once the loop ends, the respondent can proceed to the next question or submit the survey.

In SurveyJS Form Library, two question types can be used to create a breakable loop: [Dynamic Panel](/form-library/examples/duplicate-group-of-fields-in-form/) and [Dynamic Matrix](/form-library/examples/dynamic-matrix-add-new-rows/). Regardless of the question type you choose, the loop can be enabled only if your survey displays one input field per page. To enable this structure, set the [`questionsOnPageMode`](/form-library/documentation/api-reference/survey-data-model#questionsOnPageMode) property to `"inputPerPage"` at the survey level.

To implement a breakable loop based on a Dynamic Panel or Dynamic Matrix, follow the steps below:

1. Configure the repeated questions.            
Define the question configurations in the [`templateElements`](/form-library/documentation/api-reference/dynamic-panel-model#templateElements) array in a Dynamic Panel or [`columns`](/form-library/documentation/api-reference/dynamic-matrix-table-question-model#columns) array in a Dynamic Matrix.

1. Specify a title pattern.         
Each iteration step displays a title. To customize it, use the [`templateTitle`](/form-library/documentation/api-reference/dynamic-panel-model#templateTitle) property in a Dynamic Panel or [`singleInputTitleTemplate`](/form-library/documentation/api-reference/dynamic-matrix-table-question-model#singleInputTitleTemplate) property in a Dynamic Matrix. The title pattern supports text piping from other questions and loop levels.

1. *(Optional)* Define conditions for breaking the loop.        
Respondents can stop adding new entries when they want to, but you can specify conditions that end the loop automatically. For example, the [`maxPanelCount`](/form-library/documentation/api-reference/dynamic-panel-model#maxPanelCount) property in a Dynamic Panel and [`maxRowCount`](/form-library/documentation/api-reference/dynamic-matrix-table-question-model#maxRowCount) property in a Dynamic Matrix define the maximum number of repeating sections a user can add. This demo sets the `maxPanelCount` property to 5. If you need a more sophisticated logic for breaking the loop, use [triggers](/form-library/documentation/design-survey/conditional-logic#conditional-survey-logic-triggers).

For a Dynamic Panel code example, refer to the Code tab in this demo. A Dynamic Matrix code example is shown in the [Nested Loops](/form-library/examples/nested-loops/) demo.

## Files

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

```ts
export const json = {
  "questionsOnPageMode": "inputPerPage",
  "pages": [
    {
      "name": "page1",
      "elements": [
        {
          "type": "paneldynamic",
          "name": "vacation-info",
          "title": "Enter information about your vacations",
          "templateElements": [
            {
              "type": "dropdown",
              "name": "country",
              "title": "Select a country",
              "isRequired": true,
              "defaultDisplayValue": "[not selected]",
              "choicesByUrl": {
                "url": "https://surveyjs.io/api/CountriesExample",
                "valueName": "name"
              }
            },
            {
              "type": "text",
              "name": "start-date",
              "title": "Select a vacation start date",
              "isRequired": true,
              "inputType": "date"
            },
            {
              "type": "text",
              "name": "end-date",
              "title": "Select a vacation end date",
              "isRequired": true,
              "validators": [
                {
                  "type": "expression",
                  "text": "End date should be greater than start date",
                  "expression": "{panel.start-date} < {panel.end-date}"
                }
              ],
              "inputType": "date"
            },
            {
              "type": "expression",
              "name": "diffdays",
              "visible": false,
              "expression": "iif({panel.start-date} < {panel.end-date}, diffDays({panel.start-date}, {panel.end-date}), '[not set]')"
            }
          ],
          "templateTitle": "Vacation to {panel.country}, duration {panel.diffdays} days",
          "panelCount": 1,
          "maxPanelCount": 5,
          "confirmDelete": true,
          "addPanelText": "Add Vacation"
        }
      ]
    }
  ]
};
```

### `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/breakable-loop/reactjs.md)
- [Vue 3](https://surveyjs.io/form-library/examples/breakable-loop/vue3js.md)
- [jQuery](https://surveyjs.io/form-library/examples/breakable-loop/jquery.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/breakable-loop/vanillajs.md)
