Introduction

Validators are used to ensure that the values in a form meet certain requirements. They are available to Template-Driven Forms or Reactive Forms in Angular applications.

There are several built-in validators like required, email, pattern, and minLength. It is also possible to develop custom validators to address functionality that is not handled by a built-in validator.

For example, a phone number validator would consist of an input field and will not be considered valid unless the value is ten digits long.

Here is a screenshot of a phone number input field that is providing an invalid number that is nine digits long:

And here is a screenshot of a phone number input field that is providing a valid number that is ten digits long:

In this tutorial, you will construct a custom validator for a phone number input field in an Angular application.

Prerequisites

form illustration for: Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v15.2.1, npm v6.14.8, @angular/core v11.0.0, and @angular/forms v11.0.0.

Setting Up the Project

For the purpose of this tutorial, you will build from a default Angular project generated with @angular/cli.

				
					
npx @angular/cli new <^>angular-custom-validation-example<^> --style=css --routing=false --skip-tests

				
			

Note: Alternatively, you can globally install @angular/cli.

This will configure a new Angular project with styles set to "CSS" (as opposed to "Sass", Less", or "Stylus"), no routing, and skipping tests.

Navigate to the newly created project directory:

				
					
cd <^>angular-custom-validation-example<^>

				
			

At this point, you will have a new Angular project.

Using Validator in a Template-Driven Form

_Directives_ are used for validation in template-driven forms. For this example, you will create a phone-number-validator directive with @angular/cli.

First, open your terminal and use the @angular/cli package that was installed as a dev dependency to generate a new directive:

				
					
./node_modules/@angular/cli/bin/ng generate directive <^>phone-number-validator<^>

				
			

This will create phone-number-validator.directive.ts and phone-number-validator.directive.spec.ts. It will also add PhoneNumberValidatorDirective to app.module.ts.

Next, open phone-number-validator.directive.ts in your code editor. Add Validator, AbstractControl, and NG_VALIDATORS:

				
					
[label src/app/phone-number-validator.directive.ts]

import { Directive } from '@angular/core';

<^>import { AbstractControl, Validator, NG_VALIDATORS } from '@angular/forms';<^>



@Directive({

 selector: '[appPhoneNumberValidator]',

 <^>providers: [{<^>

 <^>provide: NG_VALIDATORS,<^>

 <^>useExisting: PhoneNumberValidatorDirective,<^>

 <^>multi: true<^>

 <^>}]<^>

})

export class PhoneNumberValidatorDirective <^>implements Validator<^> {

 <^>validate(control: AbstractControl) : {[key: string]: any} | null {<^>

 <^>if (control.value && control.value.length != 10) {<^>

 <^>return { 'phoneNumberInvalid': true };<^>

 <^>}<^>

 <^>return null;<^>

 <^>}<^>

}

				
			

This code creates a directive which implements Validator of @angular/forms. It will need the following implementation method: validate(control: AbstractControl): : {[key: string]: any} | null. This validator will return an object – { 'phoneNumberInvalid': true } – if the value fails the condition of not being equal to a length of ten characters. Otherwise, if the value passes the condition, it will return null.

Next, open your terminal and use the @angular/cli package that was installed as a dev dependency to generate a new directive:

				
					
./node_modules/@angular/cli/bin/ng generate component <^>template-driven-form-example<^> --flat

				
			

This command will create template-driven-form-example.component.ts and template-driven-form-example.component.html files. It will also add TemplateDrivenFormExampleComponent to app.module.ts.

Next, open template-driven-form-example.component.ts in your code editor and add phone with an initial value of an empty string:

				
					
[label src/app/template-driven-form-example.component.ts]

import { Component } from '@angular/core';



@Component({

 selector: 'app-template-driven-form-example',

 templateUrl: './template-driven-form-example.component.html',

 styleUrls: ['./template-driven-form-example.component.css']

})

export class TemplateDrivenFormExampleComponent {

 <^>phone = '';<^>

}

				
			

Angular adds the return value of the validation function in the errors property of FormControl / NgModel. If the errors property of the FormControl / NgModel is not empty then the form is invalid. If the errors property is empty then the form is valid.

To use the directive in a template-driven form, open template-driven-form-example.component.html and add the following code:

				
					
[label src/app/template-driven-form-example.component.html]

<div class="form-group">

 <label>Phone

 <input

 type="text"

 class="form-control"

 <^>name="phone"<^>

 <^>[(ngModel)]="phone"<^>

 <^>#phoneNgModel="ngModel"<^>

 <^>appPhoneNumberValidator<^>

 <^>[class.is-invalid]="(phoneNgModel.touched || phoneNgModel.dirty) && phoneNgModel.errors?.phoneNumberInvalid"<^>

 >

 </label>

 <span

 class="invalid-feedback"

 <^>*ngIf="(phoneNgModel.touched || phoneNgModel.dirty) && phoneNgModel.errors?.phoneNumberInvalid"<^>

 > 

 Phone number must be 10 digits

 </span>

</div>

				
			

This code creates an <input> element and <span> with an error message. The <input> element uses the ngModel and the appPhoneNumberValidator selector for the directive.

If the <input> has been touched or dirty and the validation is not passing, two things will occur. First, the class is-invalid will be applied to the <input>. Second, the <span> with the error message will display.

Note: Some of the classes here – form-group, form-control, invalid-feedback, and is-valid – are part of the Bootstrap Framework. These are not necessary to complete this tutorial but can provide visual aesthetics to the form.

Then, open app.module.ts in your code editor and add FormModule:

				
					
[label src/app/app.module.ts]

import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

&lt;^&gt;import { FormsModule } from '@angular/forms';&lt;^&gt;



import { AppComponent } from './app.component';

import { PhoneNumberValidatorDirective } from './phone-number-validator.directive';

import { TemplateDrivenFormExampleComponent } from './template-driven-form-example.component';



@NgModule({

 declarations: [

 AppComponent

 PhoneNumberValidatorDirective,

 TemplateDrivenFormExampleComponent

 ],

 imports: [

 BrowserModule,

 &lt;^&gt;FormsModule&lt;^&gt;

 ],

 providers: [],

 bootstrap: [AppComponent]

})

export class AppModule { }

				
			

Finally, open app.component.html and replace the content with your TemplateDrivenFormExample:

				
					
[label src/app/app.component.html]

&lt;app-template-driven-form-example&gt;&lt;/app-template-driven-form-example&gt;

				
			

You can run the npm start command and interact with your input in a web browser. If you enter less than or more than 10 characters to the phone field, it will display an error message.

At this point, you have a custom validator using a directive in a template-driven form.

Using Validator in a Reactive Forms

Instead of directives, Reactive Forms use functions for validation.

First, open your terminal and use the @angular/cli package that was installed as a dev dependency to generate a new directive:

				
					
./node_modules/@angular/cli/bin/ng generate component &lt;^&gt;reactive-form-example&lt;^&gt; --flat

				
			

This command will create reactive-form-example.component.ts and reactive-form-example.component.html files. It will also add ReactiveFormExampleComponent to app.module.ts.

Next, open reactive-form-example.component.ts in your code editor and add FormBuilder and AbstractControl:

				
					
[label src/app/reactive-form-example.component.ts]

import { Component, &lt;^&gt;OnInit&lt;^&gt; } from "@angular/core";

&lt;^&gt;import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';&lt;^&gt;



@Component({

 selector: 'app-reactive-form-example',

 templateUrl: './reactive-form-example.component.html',

 styleUrls: ['./reactive-form-example.component.css']

})

export class ReactiveFormExampleComponent &lt;^&gt;implements OnInit&lt;^&gt; {

 &lt;^&gt;myForm: FormGroup;&lt;^&gt;



 &lt;^&gt;constructor(private fb: FormBuilder) {}&lt;^&gt;



 &lt;^&gt;ngOnInit(): void {&lt;^&gt;

 &lt;^&gt;this.myForm = this.fb.group({&lt;^&gt;

 &lt;^&gt;phone: ['', [ValidatePhone]]&lt;^&gt;

 });&lt;^&gt;&lt;^&gt;

 &lt;^&gt;}&lt;^&gt;



 &lt;^&gt;saveForm(form: FormGroup) {&lt;^&gt;

 &lt;^&gt;console.log('Valid?', form.valid);&lt;^&gt; // true or false

 &lt;^&gt;console.log('Phone Number', form.value.phone);&lt;^&gt;

 &lt;^&gt;}&lt;^&gt;

}



&lt;^&gt;function ValidatePhone(control: AbstractControl): {[key: string]: any} | null {&lt;^&gt;

 &lt;^&gt;if (control.value &amp;&amp; control.value.length != 10) {&lt;^&gt;

 &lt;^&gt;return { 'phoneNumberInvalid': true };&lt;^&gt;

 &lt;^&gt;}&lt;^&gt;

 &lt;^&gt;return null;&lt;^&gt;

&lt;^&gt;}&lt;^&gt;

				
			

This code creates a ValidatePhone function and adds it to the validators array of FormControl.

Open reactive-form-example.component.html in your code editor and create the following form:

				
					
[label src/app/reactive-form-example.component.html]

&lt;form

 class="needs-validation"

 novalidate

 &lt;^&gt;[formGroup]="myForm"&lt;^&gt;

 &lt;^&gt;(ngSubmit)="saveForm(myForm)"&lt;^&gt;

&gt;

 &lt;div class="row"&gt;

 &lt;div class="form-group col-sm-4"&gt;

 &lt;label&gt;

 Phone

 &lt;input

 type="text"

 class="form-control"

 &lt;^&gt;formControlName="phone"&lt;^&gt;

 &lt;^&gt;[class.is-invalid]="(myForm.get('phone').touched || myForm.get('phone').dirty) &amp;&amp; myForm.get('phone').invalid"&lt;^&gt;

 &gt;

 &lt;/label&gt;

 &lt;span

 class="invalid-feedback"

 &lt;^&gt;*ngIf="(myForm.get('phone').touched || myForm.get('phone').dirty) &amp;&amp; myForm.get('phone').invalid"&lt;^&gt;

 &gt;

 Phone number must be 10 digits

 &lt;/span&gt; 

 &lt;/div&gt;

 &lt;/div&gt; 

&lt;/form&gt;

				
			

Unlike the template-driven form, this form features a form and uses [formGroup], (ngSubmit), formControlName, and get.

Then, open app.module.ts in your code editor and add ReactiveFormsModule:

				
					
[label src/app/app.module.ts]

import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

import { FormsModule, &lt;^&gt;ReactiveFormsModule&lt;^&gt; } from '@angular/forms';



import { AppComponent } from './app.component';

import { PhoneNumberValidatorDirective } from './phone-number-validator.directive';

import { ReactiveFormExampleComponent } from './reactive-form-example.component';

import { TemplateDrivenFormExampleComponent } from './template-driven-form-example.component';



@NgModule({

 declarations: [

 AppComponent,

 PhoneNumberValidatorDirective,

 ReactiveFormExampleComponent,

 TemplateDrivenFormExampleComponent

 ],

 imports: [

 BrowserModule,

 FormsModule,

 &lt;^&gt;ReactiveFormsModule&lt;^&gt;

 ],

 providers: [],

 bootstrap: [AppComponent]

})

export class AppModule { }

				
			

Finally, open app.component.html and replace the content with your ReactiveFormExample:

				
					
[label src/app/app.component.html]

&lt;app-reactive-form-example&gt;&lt;/app-reactive-form-example&gt;

				
			

You can run the npm start command and interact with your input in a web browser. If you enter less than or more than 10 characters to the phone field, it will display an error message.

At this point, you have a custom validator using a function in a reactive form.

Conclusion

In this article, you were introduced to adding custom validation for template-driven forms and reactive forms in an Angular application.

Custom validation allows you to ensure values provided by users fit within your expectations.

For a deeper understanding of the concepts from this post, visit this post on Providers and read about AbstractControl.