Naturally, forms in applications frequently exist to aggregate multiple instances of input into a unified behavior. One common behavior is to assess whether a form is valid, which of course requires that all of its subfields are valid. This will most commonly be achieved by bundling multiple FormControl
objects into a FormGroup
. This can be done in different ways, with varying degrees of explicitness. This recipe covers an entirely explicit implementation, that is, everything here will be created and "joined" manually.
The code, links, and a live example related to this recipe are available at .
Suppose you began with the following skeleton application:
[app/article-editor.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article-editor', template: ` <p>Title: <input></p> <p>Text: <input></p> <p><button (click)="saveArticle()">Save</button></p> <hr /> <p>Preview:</p> <div style="border:1px solid #999;margin:50px;"> <h1>{{article.title}}</h1> <p>{{article.text}}</p> </div> ` }) export class ArticleEditorComponent { article:{title:string, text:string} = {}; saveArticle():void {} }
Your goal is to update the article
object (and consequently the template) only if all the input fields are valid.
First, add the necessary code to attach new FormControl
objects to each input field and validate them with the built-in required
validator:
[app/article-editor.component.ts] import {Component} from '@angular/core'; import {FormControl, Validators} from '@angular/forms'; @Component({ selector: 'article-editor', template: ` <p>Title: <input [formControl]="titleControl"></p> <p>Text: <input [formControl]="textControl"></p> <p><button (click)="saveArticle()">Save</button></p> <hr /> <p>Preview:</p> <div style="border:1px solid #999;margin:50px;"> <h1>{{article.title}}</h1> <p>{{article.text}}</p> </div> ` }) export class ArticleEditorComponent { article:{title:string, text:string} = {}; titleControl:FormControl = new FormControl(null, Validators.required); textControl:FormControl = new FormControl(null, Validators.required); saveArticle():void {} }
At this point, you could individually inspect each input's FormControl
object and check whether it is valid. However, if this form grows to 100 fields, it would become unbearably tedious to maintain them. Therefore, you can bundle these FormControl
objects into a single FormGroup
instead:
[app/article-editor.component.ts] import {Component} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; @Component({ selector: 'article-editor', template: ` <p>Title: <input [formControl]="titleControl"></p> <p>Text: <input [formControl]="textControl"></p> <p><button (click)="saveArticle()">Save</button></p> <hr /> <p>Preview:</p> <div style="border:1px solid #999;margin:50px;"> <h1>{{article.title}}</h1> <p>{{article.text}}</p> </div> ` }) export class ArticleEditorComponent { article:{title:string, text:string} = {}; titleControl:FormControl = new FormControl(null, Validators.required); textControl:FormControl = new FormControl(null, Validators.required); articleFormGroup:FormGroup = new FormGroup({ title: this.titleControl, text: this.textControl }); saveArticle():void {} }
FormGroup
objects also expose valid and value members, so you can use these to verify and assign directly from the object:
[app/article-editor.component.ts] import {Component} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; @Component({ selector: 'article-editor', template: ` <p>Title: <input [formControl]="titleControl"></p> <p>Text: <input [formControl]="textControl"></p> <p><button (click)="saveArticle()">Save</button></p> <hr /> <p>Preview:</p> <div style="border:1px solid #999;margin:50px;"> <h1>{{article.title}}</h1> <p>{{article.text}}</p> </div> ` }) export class ArticleEditorComponent { article:{title:string, text:string} = {}; titleControl:FormControl = new FormControl(null, Validators.required); textControl:FormControl = new FormControl(null, Validators.required); articleFormGroup:FormGroup = new FormGroup({ title: this.titleControl, text: this.textControl }); saveArticle():void { if (this.articleFormGroup.valid) { this.article = this.articleFormGroup.value; } else { alert("Missing field(s)!"); } }
With this addition, your form should now be working fine.
Both FormControl
and FormGroup
inherit from the abstract base class called AbstractControl
. What this means for you is that both of them expose the same base class methods, but FormGroup
will aggregate its composition of AbstractControl
objects to be read from its own members. As you can see in the preceding code, valid
acts as a logical AND operator for all the children (meaning every single child must return true
for it to return true
); value
returns an object of the same topology as the one provided at the instantiation of FormGroup
, but with each FormControl
value instead of the FormControl
object.
As you might expect, since FormGroup
expects an object with AbstractControl
properties, you are free to nest a FormGroup
inside another FormGroup
.
You are able to access a FormGroup
's contained FormControl
members via the controls
property. The string that you used to key the FormControl
members—either upon FormGroup
instantiation, or with the addControl
method—is used to retrieve it. In this example, the text FormControl
object could be retrieved inside a component method via this.articleCtrlGroup.controls.text
.
The Angular documentation warns you to specifically not to modify the underlying FormControl
collection directly. This may lead to an undefined data binding behavior. So, always be sure to use the FormGroup
member methods addControl
and removeControl
instead of directly manipulating the collection of FormControl
objects that you pass upon instantiation.
Like Control
, a FormGroup
can have its own validators. These can be provided when the FormGroup
is instantiated, and they behave in the same way that a FormControl
validator would behave. By adding validators at the FormGroup
level, FormGroup
can override the default behavior of only being valid when all its components are valid or adding extra validation clauses.
Angular validators not only have the ability to determine whether they are valid or not, but they are also capable of returning error messages describing what is wrong. For example, when the input fields are empty, if you were to examine the errors
property of the text
FormControl
object via this.articleCtrlGroup.controls.text.errors
, it would return {required: true}
. This is the default error message of the built-in required validator. However, if you were to inspect the errors
property on the parent FormGroup
via this.articleCtrlGroup.errors
, you will find it to be null
.
This may be counter-intuitive, but it is not a mistake. Error messages will only appear on the FormControl
instance that is causing them. If you wish to aggregate error messages, you will have to traverse the nested collections of FormControl
objects manually.