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.
The code, links, and a live example related to this recipe are available at .
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.
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 {} } 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)!'); } } } 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.
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.
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.