Form Validation

Improve overall data quality by validating user input for accuracy and completeness.

This cookbook shows how to validate user input in the UI and display useful validation messages using first the template-driven forms and then the reactive forms approach.

Read more about these choices in the Forms and the Reactive Forms guides.

Contents

Try the live example to see and download the full cookbook source code.

Simple template-driven forms

In the template-driven approach, you arrange form elements in the component's template.

You add Angular form directives (mostly directives beginning ng...) to help Angular construct a corresponding internal control model that implements form functionality. In template-drive forms, the control model is implicit in the template.

To validate user input, you add HTML validation attributes to the elements. Angular interprets those as well, adding validator functions to the control model.

Angular exposes information about the state of the controls including whether the user has "touched" the control or made changes and if the control values are valid.

In this first template validation example, notice the HTML that reads the control state and updates the display appropriately. Here's an excerpt from the template HTML for a single input control bound to the hero name:

template/hero-form-template1.component.html (Hero name)

<label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" name="name" [(ngModel)]="hero.name" #name="ngModel" > <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> Name is required </div> <div [hidden]="!name.errors.minlength"> Name must be at least 4 characters long. </div> <div [hidden]="!name.errors.maxlength"> Name cannot be more than 24 characters long. </div> </div>

Note the following:

The full template repeats this kind of layout for each data entry control on the form.

Why check dirty and touched?

The app shouldn't show errors for a new hero before the user has had a chance to edit the value. The checks for dirty and touched prevent premature display of errors.

Learn about dirty and touched in the Forms guide.

The component class manages the hero model used in the data binding as well as other code to support the view.

template/hero-form-template1.component.ts (class)

export class HeroFormTemplate1Component { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } }

Use this template-driven validation technique when working with static forms with simple, standard validation rules.

Here are the complete files for the first version of HeroFormTemplateCompononent in the template-driven approach:

<div class="container"> <div [hidden]="submitted"> <h1>Hero Form 1 (Template)</h1> <form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" name="name" [(ngModel)]="hero.name" #name="ngModel" > <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> Name is required </div> <div [hidden]="!name.errors.minlength"> Name must be at least 4 characters long. </div> <div [hidden]="!name.errors.maxlength"> Name cannot be more than 24 characters long. </div> </div> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" id="alterEgo" class="form-control" name="alterEgo" [(ngModel)]="hero.alterEgo" > </div> <div class="form-group"> <label for="power">Hero Power</label> <select id="power" class="form-control" name="power" [(ngModel)]="hero.power" required #power="ngModel" > <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> <div *ngIf="power.errors && power.touched" class="alert alert-danger"> <div [hidden]="!power.errors.required">Power is required</div> </div> </div> <button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="addHero()">New Hero</button> </form> </div> <hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted> </div> import { Component } from '@angular/core'; import { Hero } from '../shared/hero'; @Component({ selector: 'hero-form-template1', templateUrl: './hero-form-template1.component.html' }) export class HeroFormTemplate1Component { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } }

Template-driven forms with validation messages in code

While the layout is straightforward, there are obvious shortcomings with the way it's handling validation messages:

In this example, you can move the logic and the messages into the component with a few changes to the template and component.

Here's the hero name again, excerpted from the revised template (Template 2), next to the original version:

<label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" forbiddenName="bob" name="name" [(ngModel)]="hero.name" > <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div> <label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" name="name" [(ngModel)]="hero.name" #name="ngModel" > <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> Name is required </div> <div [hidden]="!name.errors.minlength"> Name must be at least 4 characters long. </div> <div [hidden]="!name.errors.maxlength"> Name cannot be more than 24 characters long. </div> </div>

The <input> element HTML is almost the same. There are noteworthy differences:

Component class

The original component code for Template 1 stayed the same; however, Template 2 requires some changes in the component. This section covers the code necessary in Template 2's component class to acquire the Angular form control and compose error messages.

The first step is to acquire the form control that Angular created from the template by querying for it.

Look back at the top of the component template at the #heroForm template variable in the <form> element:

template/hero-form-template1.component.html (form tag)

<form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">

The heroForm variable is a reference to the control model that Angular derived from the template. Tell Angular to inject that model into the component class's currentForm property using a @ViewChild query:

template/hero-form-template2.component.ts (heroForm)

heroForm: NgForm; @ViewChild('heroForm') currentForm: NgForm; ngAfterViewChecked() { this.formChanged(); } formChanged() { if (this.currentForm === this.heroForm) { return; } this.heroForm = this.currentForm; if (this.heroForm) { this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); } }

Some observations:

template/hero-form-template2.component.ts (handler)

onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm.form; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } formErrors = { 'name': '', 'power': '' };

The onValueChanged handler interprets user data entry. The data object passed into the handler contains the current element values. The handler ignores them. Instead, it iterates over the fields of the component's formErrors object.

The formErrors is a dictionary of the hero fields that have validation rules and their current error messages. Only two hero properties have validation rules, name and power. The messages are empty strings when the hero data are valid.

For each field, the onValueChanged handler does the following:

Next, the component needs some error messages of course—a set for each validated property with one message per validation rule:

template/hero-form-template2.component.ts (messages)

validationMessages = { 'name': { 'required': 'Name is required.', 'minlength': 'Name must be at least 4 characters long.', 'maxlength': 'Name cannot be more than 24 characters long.', 'forbiddenName': 'Someone named "Bob" cannot be a hero.' }, 'power': { 'required': 'Power is required.' } };

Now every time the user makes a change, the onValueChanged handler checks for validation errors and produces messages accordingly.

The benefits of messages in code

Clearly the template got substantially smaller while the component code got substantially larger. It's not easy to see the benefit when there are just three fields and only two of them have validation rules.

Consider what happens as the number of validated fields and rules increases. In general, HTML is harder to read and maintain than code. The initial template was already large and threatening to get rapidly worse with the addition of more validation message <div> elements.

After moving the validation messaging to the component, the template grows more slowly and proportionally. Each field has approximately the same number of lines no matter its number of validation rules. The component also grows proportionally, at the rate of one line per validated field and one line per validation message.

Both trends are manageable.

Now that the messages are in code, you have more flexibility and can compose messages more efficiently. You can refactor the messages out of the component, perhaps to a service class that retrieves them from the server. In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.

FormModule and template-driven forms

Angular has two different forms modules—FormsModule and ReactiveFormsModule—that correspond with the two approaches to form development. Both modules come from the same @angular/forms library package.

You've been reviewing the "Template-driven" approach which requires the FormsModule. Here's how you imported it in the HeroFormTemplateModule.

template/hero-form-template.module.ts

import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { SharedModule } from '../shared/shared.module'; import { HeroFormTemplate1Component } from './hero-form-template1.component'; import { HeroFormTemplate2Component } from './hero-form-template2.component'; @NgModule({ imports: [ SharedModule, FormsModule ], declarations: [ HeroFormTemplate1Component, HeroFormTemplate2Component ], exports: [ HeroFormTemplate1Component, HeroFormTemplate2Component ] }) export class HeroFormTemplateModule { }

This guide hasn't talked about the SharedModule or its SubmittedComponent which appears at the bottom of every form template in this cookbook.

They're not germane to the validation story. Look at the live example if you're interested.

Reactive forms with validation in code

In the template-driven approach, you markup the template with form elements, validation attributes, and ng... directives from the Angular FormsModule. At runtime, Angular interprets the template and derives its form control model.

Reactive Forms takes a different approach. You create the form control model in code. You write the template with form elements and form... directives from the Angular ReactiveFormsModule. At runtime, Angular binds the template elements to your control model based on your instructions.

This approach requires a bit more effort. You have to write the control model and manage it.

This allows you to do the following:

The following cookbook sample re-writes the hero form in reactive forms style.

Switch to the ReactiveFormsModule

The reactive forms classes and directives come from the Angular ReactiveFormsModule, not the FormsModule. The application module for the reactive forms feature in this sample looks like this:

src/app/reactive/hero-form-reactive.module.ts

import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { SharedModule } from '../shared/shared.module'; import { HeroFormReactiveComponent } from './hero-form-reactive.component'; @NgModule({ imports: [ SharedModule, ReactiveFormsModule ], declarations: [ HeroFormReactiveComponent ], exports: [ HeroFormReactiveComponent ] }) export class HeroFormReactiveModule { }

The reactive forms feature module and component are in the src/app/reactive folder. Focus on the HeroFormReactiveComponent there, starting with its template.

Component template

Begin by changing the <form> tag so that it binds the Angular formGroup directive in the template to the heroForm property in the component class. The heroForm is the control model that the component class builds and maintains.

<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">

Next, modify the template HTML elements to match the reactive forms style. Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version:

<label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name" required > <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div> <label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" forbiddenName="bob" name="name" [(ngModel)]="hero.name" > <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div>

Key changes are:

A future version of reactive forms will add the required HTML validation attribute to the DOM element (and perhaps the aria-required attribute) when the control has the required validator function.

Until then, apply the required attribute and add the Validator.required function to the control model, as you'll see below.

The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.

Component class

The component class is now responsible for defining and managing the form control model.

Angular no longer derives the control model from the template so you can no longer query for it. You can create the Angular form control model explicitly with the help of the FormBuilder class.

Here's the section of code devoted to that process, paired with the template-driven code it replaces:

heroForm: FormGroup; constructor(private fb: FormBuilder) { } ngOnInit(): void { this.buildForm(); } buildForm(): void { this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), forbiddenNameValidator(/bob/i) ] ], 'alterEgo': [this.hero.alterEgo], 'power': [this.hero.power, Validators.required] }); this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now } heroForm: NgForm; @ViewChild('heroForm') currentForm: NgForm; ngAfterViewChecked() { this.formChanged(); } formChanged() { if (this.currentForm === this.heroForm) { return; } this.heroForm = this.currentForm; if (this.heroForm) { this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); } }

A real app would retrieve the hero asynchronously from a data service, a task best performed in the ngOnInit hook.

FormBuilder declaration

The FormBuilder declaration object specifies the three controls of the sample's hero form.

Each control spec is a control name with an array value. The first array element is the current value of the corresponding hero field. The optional second value is a validator function or an array of validator functions.

Most of the validator functions are stock validators provided by Angular as static methods of the Validators class. Angular has stock validators that correspond to the standard HTML validation attributes.

The forbiddenNames validator on the "name" control is a custom validator, discussed in a separate section below.

Learn more about FormBuilder in the Introduction to FormBuilder section of Reactive Forms guide.

Committing hero value changes

In two-way data binding, the user's changes flow automatically from the controls back to the data model properties. Reactive forms do not use data binding to update data model properties. The developer decides when and how to update the data model from control values.

This sample updates the model twice:

  1. When the user submits the form.
  2. When the user adds a new hero.

The onSubmit() method simply replaces the hero object with the combined values of the form:

onSubmit() { this.submitted = true; this.hero = this.heroForm.value; }

This example is lucky in that the heroForm.value properties just happen to correspond exactly to the hero data object properties.

The addHero() method discards pending changes and creates a brand new hero model object.

addHero() { this.hero = new Hero(42, '', ''); this.buildForm(); }

Then it calls buildForm() again which replaces the previous heroForm control model with a new one. The <form> tag's [formGroup] binding refreshes the page with the new control model.

Here's the complete reactive component file, compared to the two template-driven component files.

import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { Hero } from '../shared/hero'; import { forbiddenNameValidator } from '../shared/forbidden-name.directive'; @Component({ selector: 'hero-form-reactive3', templateUrl: './hero-form-reactive.component.html' }) export class HeroFormReactiveComponent implements OnInit { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; this.hero = this.heroForm.value; } } heroForm: FormGroup; constructor(private fb: FormBuilder) { } ngOnInit(): void { this.buildForm(); } buildForm(): void { this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), forbiddenNameValidator(/bob/i) ] ], 'alterEgo': [this.hero.alterEgo], 'power': [this.hero.power, Validators.required] }); this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now } onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } formErrors = { 'name': '', 'power': '' }; validationMessages = { 'name': { 'required': 'Name is required.', 'minlength': 'Name must be at least 4 characters long.', 'maxlength': 'Name cannot be more than 24 characters long.', 'forbiddenName': 'Someone named "Bob" cannot be a hero.' }, 'power': { 'required': 'Power is required.' } }; } import { Component, AfterViewChecked, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; import { Hero } from '../shared/hero'; @Component({ selector: 'hero-form-template2', templateUrl: './hero-form-template2.component.html' }) export class HeroFormTemplate2Component implements AfterViewChecked { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } heroForm: NgForm; @ViewChild('heroForm') currentForm: NgForm; ngAfterViewChecked() { this.formChanged(); } formChanged() { if (this.currentForm === this.heroForm) { return; } this.heroForm = this.currentForm; if (this.heroForm) { this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); } } onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm.form; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } formErrors = { 'name': '', 'power': '' }; validationMessages = { 'name': { 'required': 'Name is required.', 'minlength': 'Name must be at least 4 characters long.', 'maxlength': 'Name cannot be more than 24 characters long.', 'forbiddenName': 'Someone named "Bob" cannot be a hero.' }, 'power': { 'required': 'Power is required.' } }; } import { Component } from '@angular/core'; import { Hero } from '../shared/hero'; @Component({ selector: 'hero-form-template1', templateUrl: './hero-form-template1.component.html' }) export class HeroFormTemplate1Component { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } }

Run the live example to see how the reactive form behaves, and to compare all of the files in this cookbook sample.

Custom validation

This cookbook sample has a custom forbiddenNamevalidator() function that's applied to both the template-driven and the reactive form controls. It's in the src/app/shared folder and declared in the SharedModule.

Here's the forbiddenNamevalidator() function:

shared/forbidden-name.directive.ts (forbiddenNameValidator)

/** A hero's name can't match the given regular expression */ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): {[key: string]: any} => { const name = control.value; const no = nameRe.test(name); return no ? {'forbiddenName': {name}} : null; }; }

The function is actually a factory that takes a regular expression to detect a specific forbidden name and returns a validator function.

In this sample, the forbidden name is "bob"; the validator rejects any hero name containing "bob". Elsewhere it could reject "alice" or any name that the configuring regular expression matches.

The forbiddenNameValidator factory returns the configured validator function. That function takes an Angular control object and returns either null if the control value is valid or a validation error object. The validation error object typically has a property whose name is the validation key, 'forbiddenName', and whose value is an arbitrary dictionary of values that you could insert into an error message ({name}).

Custom validation directive

In the reactive forms component, the 'name' control's validator function list has a forbiddenNameValidator at the bottom.

reactive/hero-form-reactive.component.ts (name validators)

'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), forbiddenNameValidator(/bob/i) ] ],

In the template-driven example, the <input> has the selector (forbiddenName) of a custom attribute directive, which rejects "bob".

template/hero-form-template2.component.html (name input)

<input type="text" id="name" class="form-control" required minlength="4" maxlength="24" forbiddenName="bob" name="name" [(ngModel)]="hero.name" >

The corresponding ForbiddenValidatorDirective is a wrapper around the forbiddenNameValidator.

Angular forms recognizes the directive's role in the validation process because the directive registers itself with the NG_VALIDATORS provider, a provider with an extensible collection of validation directives.

shared/forbidden-name.directive.ts (providers)

providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]

Here is the rest of the directive to help you get an idea of how it all comes together:

shared/forbidden-name.directive.ts (directive)

@Directive({ selector: '[forbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}] }) export class ForbiddenValidatorDirective implements Validator, OnChanges { @Input() forbiddenName: string; private valFn = Validators.nullValidator; ngOnChanges(changes: SimpleChanges): void { const change = changes['forbiddenName']; if (change) { const val: string | RegExp = change.currentValue; const re = val instanceof RegExp ? val : new RegExp(val, 'i'); this.valFn = forbiddenNameValidator(re); } else { this.valFn = Validators.nullValidator; } } validate(control: AbstractControl): {[key: string]: any} { return this.valFn(control); } }

If you are familiar with Angular validations, you may have noticed that the custom validation directive is instantiated with useExisting rather than useClass. The registered validator must be this instance of the ForbiddenValidatorDirective—the instance in the form with its forbiddenName property bound to “bob". If you were to replace useExisting with useClass, then you’d be registering a new class instance, one that doesn’t have a forbiddenName.

To see this in action, run the example and then type “bob” in the name of Hero Form 2. Notice that you get a validation error. Now change from useExisting to useClass and try again. This time, when you type “bob”, there's no "bob" error message.

For more information on attaching behavior to elements, see Attribute Directives.

Testing Considerations

You can write isolated unit tests of validation and control logic in Reactive Forms.

Isolated unit tests probe the component class directly, independent of its interactions with its template, the DOM, other dependencies, or Angular itself.

Such tests have minimal setup, are quick to write, and easy to maintain. They do not require the Angular TestBed or asynchronous testing practices.

That's not possible with template-driven forms. The template-driven approach relies on Angular to produce the control model and to derive validation rules from the HTML validation attributes. You must use the Angular TestBed to create component test instances, write asynchronous tests, and interact with the DOM.

While not difficult, this takes more time, work and skill—factors that tend to diminish test code coverage and quality.