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.
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.
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.
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
:
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:
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:
<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
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
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,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Finally, open app.component.html
and replace the content with your TemplateDrivenFormExample
:
<app-template-driven-form-example></app-template-driven-form-example>
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.
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 reactive-form-example --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
:
import { Component, OnInit } from "@angular/core";
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-reactive-form-example',
templateUrl: './reactive-form-example.component.html',
styleUrls: ['./reactive-form-example.component.css']
})
export class ReactiveFormExampleComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.myForm = this.fb.group({
phone: ['', [ValidatePhone]]
});
}
saveForm(form: FormGroup) {
console.log('Valid?', form.valid); // true or false
console.log('Phone Number', form.value.phone);
}
}
function ValidatePhone(control: AbstractControl): {[key: string]: any} | null {
if (control.value && control.value.length != 10) {
return { 'phoneNumberInvalid': true };
}
return null;
}
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:
<form
class="needs-validation"
novalidate
[formGroup]="myForm"
(ngSubmit)="saveForm(myForm)"
>
<div class="row">
<div class="form-group col-sm-4">
<label>
Phone
<input
type="text"
class="form-control"
formControlName="phone"
[class.is-invalid]="(myForm.get('phone').touched || myForm.get('phone').dirty) && myForm.get('phone').invalid"
>
</label>
<span
class="invalid-feedback"
*ngIf="(myForm.get('phone').touched || myForm.get('phone').dirty) && myForm.get('phone').invalid"
>
Phone number must be 10 digits
</span>
</div>
</div>
</form>
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
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } 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,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Finally, open app.component.html
and replace the content with your ReactiveFormExample
:
<app-reactive-form-example></app-reactive-form-example>
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.
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
.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Way simpler and easier to understand than other similar tutorials
This was tricky but a really helpful post! Thanks!!!