You are on page 1of 25

11/20/2016 Form 

Validation ­ ts ­ COOKBOOK

FORM VALIDATION

Validate user's form entries

We can improve overall data quality by validating user input for accuracy and
completeness.

In this cookbook we show how to validate user input in the UI and display useful validation
messages using 堀rst the template-driven forms and then the reactive forms approach.

Learn more about these choices in the Forms chapter.

Table of Contents
Simple Template-Driven Forms

Template-Driven Forms with validation messages in code

Reactive Forms with validation in code

Custom validation

Testing

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

https://angular.io/docs/ts/latest/cookbook/form­validation.html 1/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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. We
say that 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 the 堀rst template validation example, we add more HTML to read that control state and
https://angular.io/docs/ts/latest/cookbook/form­validation.html 2/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

update the display appropriately. Here's an excerpt from the template html for a single
input box 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 <input> element carries the HTML validation attributes: required ,


minlength , and maxlength .

We set the name attribute of the input box to "name" so Angular can track this input
element and associate it with an Angular form control called name in its internal
control model.

We use the [(ngModel)] directive to two-way data bind the input box to the
hero.name property.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 3/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

We set a template variable ( #name ) to the value "ngModel" (always ngModel ).


This gives us a reference to the Angular NgModel directive associated with this
control that we can use in the template to check for control states such as valid
and dirty .

The *ngIf on <div> element reveals a set of nested message divs but only if
there are "name" errors and the control is either dirty or touched .

Each nested <div> can present a custom message for one of the possible
validation errors. We've prepared messages for required , minlength , and
maxlength .

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

WHY CHECK DIRTY AND TOUCHED?

We 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 chapter.

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)

1. export class HeroFormTemplate1Component {


2.

3. powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];


4.

5. hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0],

https://angular.io/docs/ts/latest/cookbook/form­validation.html 4/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

'Dr. What');
6.

7. submitted = false;
8.

9. onSubmit() {
10. this.submitted = true;
11. }
12.

13. addHero() {
14. this.hero = new Hero(42, '', '');
15. }
16. }

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

Here are the complete 堀les for the 堀rst version of HeroFormTemplateCompononent in
the template-driven approach:

1. <div class="container">
2. <div [hidden]="submitted">
3. <h1>Hero Form 1 (Template)</h1>
4. <form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">
5. <div class="form-group">
6. <label for="name">Name</label>
7.

8. <input type="text" id="name" class="form-control"


9. required minlength="4" maxlength="24"
10. name="name" [(ngModel)]="hero.name"
11. #name="ngModel" >
12.

13. <div *ngIf="name.errors && (name.dirty || name.touched)"


14. class="alert alert-danger">
15. <div [hidden]="!name.errors.required">
16. Name is required
17. </div>

https://angular.io/docs/ts/latest/cookbook/form­validation.html 5/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

18. <div [hidden]="!name.errors.minlength">


19. Name must be at least 4 characters long.
20. </div>
21. <div [hidden]="!name.errors.maxlength">
22. Name cannot be more than 24 characters long.
23. </div>
24. </div>
25. </div>
26.

27. <div class="form-group">


28. <label for="alterEgo">Alter Ego</label>
29. <input type="text" id="alterEgo" class="form-control"
30. name="alterEgo"
31. [(ngModel)]="hero.alterEgo" >
32. </div>
33.

34. <div class="form-group">


35. <label for="power">Hero Power</label>
36. <select id="power" class="form-control"
37. name="power"
38. [(ngModel)]="hero.power" required
39. #power="ngModel" >
40. <option *ngFor="let p of powers" [value]="p">{{p}}</option>
41. </select>
42.

43. <div *ngIf="power.errors && power.touched" class="alert alert-


danger">
44. <div [hidden]="!power.errors.required">Power is required</div>
45. </div>
46. </div>
47.

48. <button type="submit" class="btn btn-default"


49. [disabled]="!heroForm.form.valid">Submit</button>
50. <button type="button" class="btn btn-default"
51. (click)="addHero()">New Hero</button>
52. </form>
53. </div>
54.

55. <hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-


submitted>

https://angular.io/docs/ts/latest/cookbook/form­validation.html 6/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

56. </div>

Template-Driven Forms with validation messages in code


While the layout is straightforward, there are obvious shortcomings with the way we
handle validation messages:

It takes a lot of HTML to represent all possible error conditions. This gets out of hand
when there are many controls and many validation rules.

We're not fond of so much JavaScript logic in HTML.

The messages are static strings, hard-coded into the template. We often require
dynamic messages that we should shape in code.

We 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:

1. <label for="name">Name</label>
2.

3. <input type="text" id="name" class="form-control"


4. required minlength="4" maxlength="24" forbiddenName="bob"
5. name="name" [(ngModel)]="hero.name" >
6.

7. <div *ngIf="formErrors.name" class="alert alert-danger">


8. {{ formErrors.name }}
9. </div>

https://angular.io/docs/ts/latest/cookbook/form­validation.html 7/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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

The hard-code error message <divs> are gone.

There's a new attribute, forbiddenName , that is actually a custom validation


directive. It invalidates the control if the user enters "bob" anywhere in the name (try
it). We discuss custom validation directives later in this cookbook.

The #name template variable is gone because we no longer refer to the Angular
control for this element.

Binding to the new formErrors.name property is suf堀cent to display all name


validation error messages.

COMPONENT CLASS

The original component code stays the same. We added new code to acquire the Angular
form control and compose error messages.

The 堀rst 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 where we set 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. We tell Angular to inject that model into the component class's currentForm
property using a @ViewChild query:

template/hero-form-template2.component.ts (heroForm)
https://angular.io/docs/ts/latest/cookbook/form­validation.html 8/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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:

Angular @ViewChild queries for a template variable when you pass it the name of
that variable as a string ( 'heroForm' in this case).

The heroForm object changes several times during the life of the component, most
notably when we add a new hero. We'll have to re-inspect it periodically.

Angular calls the ngAfterViewChecked lifecycle hook method when anything


changes in the view. That's the right time to see if there's a new heroForm object.

When there is a new heroForm model, we subscribe to its valueChanged


Observable property. The onValueChanged handler looks for validation errors after
every user keystroke.

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

onValueChanged(data?: any) {
if (!this.heroForm) { return; }

https://angular.io/docs/ts/latest/cookbook/form­validation.html 9/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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 堀elds of the component's formErrors object.

The formErrors is a dictionary of the hero 堀elds 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 堀eld, the handler

clears the prior error message if any


acquires the 堀eld's corresponding Angular form control
if such a control exists and its been changed ("dirty") and its invalid ...
the handler composes a consolidated error message for all of the control's errors.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 10/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

We'll need some error messages of course, a set for each validated property, 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.

Is this an improvement?
Clearly the template got substantially smaller while the component code got substantially
larger. It's not easy to see the bene堀t when there are just three 堀elds and only two of them
have validation rules.

Consider what happens as we increase the number of validated 堀elds and rules. In
general, HTML is harder to read and maintain than code. The initial template was already
large and threatening to get rapidly worse as we add more validation message <divs> .

After moving the validation messaging to the component, the template grows more slowly
and proportionally. Each 堀eld 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 堀eld and one line per validation message.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 11/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

Both trends are manageable.

Now that the messages are in code, we have more 儀exibility. We can compose messages
more intelligently. We 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.

We've been reviewing the "Template-driven" approach which requires the FormsModule
Here's how we 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 { }

https://angular.io/docs/ts/latest/cookbook/form­validation.html 12/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

We haven'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
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.

In return, you can

add, change, and remove validation functions on the 儀y


manipulate the control model dynamically from within the component
test validation and control logic with isolated unit tests.

The third 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

https://angular.io/docs/ts/latest/cookbook/form­validation.html 13/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

"Reactive Forms" feature in this sample looks like this:

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 app/reactive folder.
Let's focus on the HeroFormReactiveComponent there, starting with its template.

Component template
We 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()">

Then we 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:

https://angular.io/docs/ts/latest/cookbook/form­validation.html 14/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

1. <label for="name">Name</label>
2.

3. <input type="text" id="name" class="form-control"


4. formControlName="name" required >
5.

6. <div *ngIf="formErrors.name" class="alert alert-danger">


7. {{ formErrors.name }}
8. </div>

Key changes:

the validation attributes are gone (except required ) because we'll be validating in
code.

required remains, not for validation purposes (we'll cover that in the code), but

rather for css styling and accessibility.

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 we'll do below.

the formControlName replaces the name attribute; it serves the same purpose of
correlating the input box with the Angular form control.

the two-way [(ngModel)] binding is gone. The reactive approach does not use data
binding to move data into and out of the form controls. We do that in code.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 15/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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 de堀ning and managing the form control
model.

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

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

1. heroForm: FormGroup;
2. constructor(private fb: FormBuilder) { }
3.

4. ngOnInit(): void {
5. this.buildForm();
6. }
7.

8. buildForm(): void {
9. this.heroForm = this.fb.group({
10. 'name': [this.hero.name, [
11. Validators.required,
12. Validators.minLength(4),
13. Validators.maxLength(24),
14. forbiddenNameValidator(/bob/i)
15. ]
16. ],
17. 'alterEgo': [this.hero.alterEgo],
18. 'power': [this.hero.power, Validators.required]
19. });

https://angular.io/docs/ts/latest/cookbook/form­validation.html 16/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

20.

21. this.heroForm.valueChanges
22. .subscribe(data => this.onValueChanged(data));
23.

24. this.onValueChanged(); // (re)set validation messages now


25. }

we inject the FormBuilder in a constructor.

we call a buildForm method in the ngOnInit lifecycle hook method because that's
when we'll have the hero data. We'll call it again in the addHero method.

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

the buildForm method uses the FormBuilder ( fb ) to declare the form control
model. Then it attaches the same onValueChanged handler (there's a one line
difference) to the form's valueChanged event and calls it immediately to set error
messages for the new control model.

FORMBUILDER DECLARATION

The FormBuilder declaration object speci堀es the three controls of the sample's hero
form.

Each control spec is a control name with an array value. The 堀rst array element is the
current value of the corresponding hero 堀eld. 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

https://angular.io/docs/ts/latest/cookbook/form­validation.html 17/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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 a forthcoming chapter on reactive forms.

COMMITTING HERO VALUE CHANGES

In two-way data binding, the user's changes 儀ow 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 chooses to add 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.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 18/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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 堀le, compared to the two template-driven
component 堀les.

1. import { Component, OnInit } from '@angular/core';


2. import { FormGroup, FormBuilder, Validators } from '@angular/forms';
3.

4. import { Hero } from '../shared/hero';


5. import { forbiddenNameValidator } from '../shared/forbidden-
name.directive';
6.

7. @Component({
8. moduleId: module.id,
9. selector: 'hero-form-reactive3',
10. templateUrl: 'hero-form-reactive.component.html'
11. })
12. export class HeroFormReactiveComponent implements OnInit {
13.

14. powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];


15.

16. hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What');


17.

18. submitted = false;


19.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 19/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

20. onSubmit() {
21. this.submitted = true;
22. this.hero = this.heroForm.value;
23. }
24. addHero() {
25. this.hero = new Hero(42, '', '');
26. this.buildForm();
27.

28. this.active = false;


29. setTimeout(() => this.active = true, 0);
30. }
31.

32. heroForm: FormGroup;


33. constructor(private fb: FormBuilder) { }
34.

35. ngOnInit(): void {


36. this.buildForm();
37. }
38.

39. buildForm(): void {


40. this.heroForm = this.fb.group({
41. 'name': [this.hero.name, [
42. Validators.required,
43. Validators.minLength(4),
44. Validators.maxLength(24),
45. forbiddenNameValidator(/bob/i)
46. ]
47. ],
48. 'alterEgo': [this.hero.alterEgo],
49. 'power': [this.hero.power, Validators.required]
50. });
51.

52. this.heroForm.valueChanges
53. .subscribe(data => this.onValueChanged(data));
54.

55. this.onValueChanged(); // (re)set validation messages now


56. }
57.

58.

59. onValueChanged(data?: any) {

https://angular.io/docs/ts/latest/cookbook/form­validation.html 20/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

60. if (!this.heroForm) { return; }


61. const form = this.heroForm;
62.

63. for (const field in this.formErrors) {


64. // clear previous error message (if any)
65. this.formErrors[field] = '';
66. const control = form.get(field);
67.

68. if (control && control.dirty && !control.valid) {


69. const messages = this.validationMessages[field];
70. for (const key in control.errors) {
71. this.formErrors[field] += messages[key] + ' ';
72. }
73. }
74. }
75. }
76.

77. formErrors = {
78. 'name': '',
79. 'power': ''
80. };
81.

82. validationMessages = {
83. 'name': {
84. 'required': 'Name is required.',
85. 'minlength': 'Name must be at least 4 characters long.',
86. 'maxlength': 'Name cannot be more than 24 characters long.',
87. 'forbiddenName': 'Someone named "Bob" cannot be a hero.'
88. },
89. 'power': {
90. 'required': 'Power is required.'
91. }
92. };
93. }

Run the live example to see how the reactive form behaves and to compare all of

https://angular.io/docs/ts/latest/cookbook/form­validation.html 21/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

the 堀les 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 app/shared folder
and declared in the SharedModule .

Here's the forbiddenNamevalidator function itself:

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 speci c
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 con堀guring regular expression
matches.

The forbiddenNamevalidator factory returns the con堀gured 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

https://angular.io/docs/ts/latest/cookbook/form­validation.html 22/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

values that we could insert into an error message ( {name} ).

Learn more about validator functions in a forthcoming chapter on custom form


validation.

CUSTOM VALIDATION DIRECTIVE

In the reactive forms component we added a con堀gured forbiddenNamevalidator to


the bottom of the 'name' control's validator function list.

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 component template, we add the selector ( forbiddenName ) of a


custom attribute directive to the name's input box and con堀gured it to reject "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 .

https://angular.io/docs/ts/latest/cookbook/form­validation.html 23/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

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}]

The rest of the directive is unremarkable and we present it here without further comment.

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

1. @Directive({
2. selector: '[forbiddenName]',
3. providers: [{provide: NG_VALIDATORS, useExisting:
ForbiddenValidatorDirective, multi: true}]
4. })
5. export class ForbiddenValidatorDirective implements Validator, OnChanges
{
6. @Input() forbiddenName: string;
7. private valFn = Validators.nullValidator;
8.

9. ngOnChanges(changes: SimpleChanges): void {


10. const change = changes['forbiddenName'];
11. if (change) {
12. const val: string | RegExp = change.currentValue;
13. const re = val instanceof RegExp ? val : new RegExp(val, 'i');
14. this.valFn = forbiddenNameValidator(re);
15. } else {
16. this.valFn = Validators.nullValidator;
17. }
18. }
19.

20. validate(control: AbstractControl): {[key: string]: any} {


21. return this.valFn(control);

https://angular.io/docs/ts/latest/cookbook/form­validation.html 24/25
11/20/2016 Form Validation ­ ts ­ COOKBOOK

22. }
23. }

See the Attribute Directives chapter.

Testing Considerations
We 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 dif堀cult, this takes more time, work and skill — factors that tend to diminish test
code coverage and quality.

https://angular.io/docs/ts/latest/cookbook/form­validation.html 25/25

You might also like