Книга: Angular 2 Cookbook
Назад: Bundling controls with a FormGroup
Дальше: Implementing basic forms with NgForm

Bundling FormControls with a FormArray

You will most likely find that FormGroups are more than capable of serving your needs for the purpose of combining many FormControl objects into one container. However, there is one very common pattern that makes its sister type, the FormArray, extremely useful: variable length cloned inputs.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

Suppose you had the following skeleton application:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl, Validators}      from '@angular/forms';      @Component({     selector: 'article-editor',     template: `       <p>Tags:</p>       <ul>         <li *ngFor="let t of tagControls; let i = index">           <input [formControl]="t">         </li>       </ul>       <p><button (click)="addTag()">+</button></p>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     tagControls:Array<FormControl> = [];          addTag():void {}     saveArticle():void {}   }   

Your objective is to modify this component so that an arbitrary number of tags can be added and so all the tags can be validated together.

How to do it...

In many ways, a FormArray behaves more or less identically to a FormGroup. It is imported in the same way and inherited from AbstractControl. Also, it is instantiated in a similar way and can add and remove FormControl instances. First, add the boilerplate to your application; this will allow you to instantiate an instance of a FormArray and pass it the array of FormControl objects already inside the component. Since you already have a button that is meant to invoke the addTag method, you should also configure this method to push a new FormControl on to tagControl:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl, FormArray, Validators}      from '@angular/forms';      @Component({     selector: 'article-editor',     template: `       <p>Tags:</p>       <ul>         <li *ngFor="let t of tagControls; let i = index">           <input [formControl]="t">         </li>       </ul>       <p><button (click)="addTag()">+</button></p>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     tagControls:Array<FormControl> = [];     tagFormArray:FormArray = new FormArray(this.tagControls);          addTag():void {       this.tagFormArray         .push(new FormControl(null, Validators.required));     }     saveArticle():void {}   }   

Note

At this point, it's important that you don't confuse yourself with what you are working with. Inside this ArticleEditor component, you have an array of FormControl objects (tagControls) and you also have a single instance of FormArray (tagFormArray). The FormArray instance is initialized by being passed the array of FormControl objects, which it will then be able to manage.

Now that your FormArray is managing the tag's FormControl objects, you can safely use its validator:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl, FormArray, Validators}      from '@angular/forms';      @Component({     selector: 'article-editor',     template: `       <p>Tags:</p>       <ul>         <li *ngFor="let t of tagControls; let i = index">           <input [formControl]="t">         </li>       </ul>       <p><button (click)="addTag()">+</button></p>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     tagControls:Array<FormControl> = [];     tagFormArray:FormArray = new FormArray(this.tagControls);          addTag():void {       this.tagFormArray         .push(new FormControl(null, Validators.required));     }     saveArticle():void {       if (this.tagFormArray.valid) {         alert('Valid!');       } else {         alert('Missing field(s)!');       }     }   }   

How it works...

Because the template is reacting to the click event, you are able to use Angular data binding to automatically update the template. However, it is extremely important that you note the asymmetry in this example. The template is iterating through the tagControls array. However, when you want to add a new FormControl object, you push it to tagFormArray, which will in turn push it to the tagControls array. The FormArray object acts as the manager of the collection of FormControl objects, and all modifications of this collection should go through the manager, not the collection itself.

Tip

The Angular documentation warns you to specifically not modify the underlying FormControl collection directly. This may lead to undefined data binding behavior, so always be sure to use the FormArray members push, insert, and removeAt instead of directly manipulating the array of FormControl objects that you pass upon instantiation.

There's more...

You can take this example one step further by adding the ability to remove from this list as well. Since you already have the index inside the template repeater and FormArray offers index-based removal, this is simple to implement:

[app/article-editor.component.ts]      import {Component} from '@angular/core';   import {FormControl, FormArray, Validators}      from '@angular/forms';      @Component({     selector: 'article-editor',     template: `       <p>Tags:</p>       <ul>         <li *ngFor="let t of tagControls; let i = index">           <input [formControl]="t">           <button (click)="removeTag(i)">X</button>         </li>       </ul>       <p><button (click)="addTag()">+</button></p>       <p><button (click)="saveArticle()">Save</button></p>     `   })   export class ArticleEditorComponent {     tagControls:Array<FormControl> = [];     tagFormArray:FormArray = new FormArray(this.tagControls);          addTag():void {       this.tagFormArray         .push(new FormControl(null, Validators.required));     }     removeTag(idx:number):void {       this.tagFormArray.removeAt(idx);     }     saveArticle():void {       if (this.tagFormArray.valid) {         alert('Valid!');       } else {         alert('Missing field(s)!');       }     }   }

This allows you to cleanly insert and remove FormControl instances while letting Angular data binding do all of the work for you.

See also

  • Implementing simple two-way data binding with ngModel demonstrates the new way in Angular 2 to control bidirectional data flow
  • Implementing basic forms with ngForm demonstrates Angular's declarative form construction
  • Implementing basic forms with FormBuilder and formControlName shows how to use the FormBuilder service to quickly put together nested forms
Назад: Bundling controls with a FormGroup
Дальше: Implementing basic forms with NgForm

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already