In this article, will explore how angular reactive forms validation works. One of the common tasks that is performed, while building a form is Validation. We will show you how to build Reactive Forms and apply Built-in validators. In the next tutorial, you will learn how to create custom validators in Reactive Forms.
We looked at how the validation works in the Template-driven Forms tutorial. Most of the concepts explained in that tutorial are equally applicable here.
Table of Contents
Validators in Reactive Forms
What is a Validator
A Validator is a function that checks the instance of FormControl
, FormGroup
or a FormArray
and returns a list of errors. If the Validator returns a null means that validation has passed
How to add a Validator to Reactive Forms
We configure the validators as the second and third argument to the FormControl
, FormGroup
or FormArray
in the component class. The second argument is a collection of sync validators and the third argument is a collection of an async validators.
sync validators
runs validations and returns immediately. They either return a list of errors or null if no errors found.
async validators:
returns a Promise or Observable. They either return a list of errors or null if no errors are found.
Built-in Validators
The Angular ReactiveForms
Module provides several Built-in validators out of the box. They are required
, minlength
, maxlength
& pattern
etc.
Reactive Forms Validation Example
W learned how to create Angular Reactive Forms in the previous tutorial. We will now add some of the built-in validators to that example.
Model
Here is the contactForm
model from the previous tutorial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | contactForm = new FormGroup({ firstname: new FormControl(''), lastname: new FormControl(''), email:new FormControl(''), gender: new FormControl(''), isMarried: new FormControl(''), country: new FormControl(''), address:new FormGroup({ city: new FormControl(''), street: new FormControl(''), pincode:new FormControl('') }) }) |
Disabling the Browser validation
First, we need to disable browser validator by adding the novalidate attribute to the <form>
element as shown below. If this attribute is present then the form is not validated by the built-in HTML5 validation when submitted.
1 2 3 | <form [formGroup]="contactForm" (ngSubmit)="onSubmit()" novalidate> |
Adding in Built-in Validators
The mentioned earlier, the Angular has provided several built-in validators out of the box.
Required Validator
The required validator is a sync validator, which returns true only if the formcontrol
has a non-empty value entered. The second argument of the FormControl takes the Sync Validator.
1 2 3 | firstname: new FormControl('',[Validators.required]), |
Minlength Validator
Minlength validator requires the control value must not have less number of characters than the value specified in the validator.
For Example, minlength
validator ensures that the firstname
value has at least 10 characters.
1 2 3 | firstname: new FormControl('',[Validators.required,Validators.minLength(10)]), |
Maxlength Validator
This Validator requires that the number of characters must not exceed the value specified in the validator.
1 2 3 | lastname: new FormControl('',[Validators.maxLength(15)]), |
Pattern Validator
This Validator requires that the control value must match the regex pattern provided in the attribute. For example, the pattern ^[a-zA-Z]+$
ensures that the only letters are allowed (even spaces are not allowed). Let us apply this pattern to the lastName
1 2 3 | lastname: new FormControl('',[Validators.maxLength(15), Validators.pattern("^[a-zA-Z]+$")]), |
Email Validator
This Validator requires that the control value must be a valid email address. We apply this to the email field
1 2 3 | email:new FormControl('',[Validators.email,Validators.required]), |
After adding all the validators, our final contactForm
will look like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | contactForm = new FormGroup({ firstname: new FormControl('',[Validators.required,Validators.minLength(10)]), lastname: new FormControl('',[Validators.required, Validators.maxLength(15), Validators.pattern("^[a-zA-Z]+$")]), email:new FormControl('',[Validators.email,Validators.required]), gender: new FormControl('',[Validators.required]), isMarried: new FormControl('',[Validators.required]), country: new FormControl('',[Validators.required]), address:new FormGroup({ city: new FormControl('',[Validators.required]), street: new FormControl('',[Validators.required]), pincode:new FormControl('',[Validators.required]) }) }) |
We have successfully added the validators. Now, we need to disable the submit button if our form is not valid.
The Angular Forms API exposes the state of the forms through the FormGroup
, FormControl
& FormArray
instances. The FormGroup
control has a property valid
, which is set to true
if all of its child controls are valid
.
The contactForm
represents the top-level FormGroup
. We use it to set the disabled
attribute of the submit button.
1 2 3 | <button type="submit" [disabled]="!contactForm.valid">Submit</button> |
Displaying the Validation/Error messages
We need to provide a short and meaningful error message to the user. We do that by using the error object returned by the FormControl
instance
Every form element has a FormControl
instance associated with it. It exposes the state of form element like valid
, dirty
, touched
etc.
There are two ways in which you can get the reference to the FormControl
.
One way is to use the contactForm
variable. We can use contactForm.controls.firstname.valid
to find out if the firstname
is valid.
1 2 3 4 5 6 7 | <div *ngIf="!contactForm.controls.firstname?.valid && (contactForm.controls.firstname?.dirty ||contactForm.controls.firstname?.touched)"> First Name is not valid </div> |
The other way to is to define getter function for each FormControl
instance in the component class.
1 2 3 4 5 | get firstname() { return this.contactForm.get('firstname'); } |
and then use it in the template as follows
1 2 3 4 5 | <div *ngIf="!firstname.valid && (firstname.dirty ||firstname.touched)"> First Name is not valid </div> |
Dirty & touched
Apart from checking valid
we are also checking for the dirty
& touched
. Because we do not want the application to display the error when the form is displayed for the first time. We want to display errors only after the user has attempted to change the value. The dirty
& touched
properties help us do that.
dirty:
A control is dirty
if the user has changed the value in the UI. touched:
A control is touched if the user has triggered a blur
event on it.
Error message
The error message “First Name is not valid ” is not helpful. The firstname has two validators. required
and minlength
Any errors generated by the failing validation is updated in the errors
object. The errors
object returns the error object or null
if there are no errors.
1 2 3 4 5 6 7 8 9 10 11 | <div *ngIf="!firstname?.valid && (firstname?.dirty ||firstname?.touched)"> <div [hidden]="!firstname.errors.required"> First Name is required </div> <div [hidden]="!firstname.errors.minlength"> Min Length is 10 </div> </div> |
Final Code
app.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | import { Component, ViewChild, ElementRef } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms' @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Angular Reactive forms'; contactForm = new FormGroup({ firstname: new FormControl('',[Validators.required,Validators.minLength(10)]), lastname: new FormControl('',[Validators.required, Validators.maxLength(15), Validators.pattern("^[a-zA-Z]+$")]), email:new FormControl('',[Validators.email,Validators.required]), gender: new FormControl('',[Validators.required]), isMarried: new FormControl('',[Validators.required]), country: new FormControl('',[Validators.required]), address:new FormGroup({ city: new FormControl('',[Validators.required]), street: new FormControl('',[Validators.required]), pincode:new FormControl('',[Validators.required]) }) }) get firstname() { return this.contactForm.get('firstname'); } get lastname() { return this.contactForm.get('lastname'); } get email() { return this.contactForm.get('email'); } get gender() { return this.contactForm.get('gender'); } get isMarried() { return this.contactForm.get('isMarried'); } get country() { return this.contactForm.get('country'); } get city() { return this.contactForm.get("address").get('city'); } get street() { return this.contactForm.get("address").get('street'); } get pincode() { return this.contactForm.get("address").get('pincode'); } countryList: country[] = [ new country("1", "India"), new country('2', 'USA'), new country('3', 'England') ]; onSubmit() { console.log(this.contactForm.value); } } export class contact { firstname:string; lastname:string; gender:string; isMarried:boolean; country:string; address: { city:string; street:string; pincode:string; } } export class country { id: string; name: string; constructor(id: string, name: string) { this.id = id; this.name = name; } } |
app.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | <form [formGroup]="contactForm" (ngSubmit)="onSubmit()" novalidate> <p> <label for="firstname">First Name </label> <input type="text" id="firstname" name="firstname" formControlName="firstname"> </p> <div *ngIf="!firstname?.valid && (firstname?.dirty ||firstname?.touched)"> <div [hidden]="!firstname.errors.required"> First Name is required </div> <div [hidden]="!firstname.errors.minlength"> Min Length is 10 </div> </div> <p> <label for="lastname">Last Name </label> <input type="text" id="lastname" name="lastname" formControlName="lastname"> </p> <div *ngIf="!lastname.valid && (lastname.dirty ||lastname.touched)"> <div [hidden]="!lastname.errors.pattern"> Only characters are allowed </div> <div [hidden]="!lastname.errors.maxLength"> Max length allowed is {{lastname.errors.maxlength?.requiredLength}} </div> <div [hidden]="!lastname.errors.required"> Last Name is required </div> </div> <p> <label for="email">Email </label> <input type="text" id="email" name="email" formControlName="email"> </p> <div *ngIf="!email.valid && (email.dirty ||email.touched)"> <div [hidden]="!email.errors.required"> email is required </div> <div [hidden]="!email.errors.email"> invalid email id </div> </div> <p> <label for="gender">Geneder </label> <input type="radio" value="male" id="gender" name="gender" formControlName="gender"> Male <input type="radio" value="female" id="gender" name="gender" formControlName="gender"> Female </p> <div *ngIf="!gender.valid && (gender.dirty ||gender.touched)"> <div [hidden]="!gender.errors.required"> gender is required </div> </div> <p> <label for="isMarried">Married </label> <input type="checkbox" id="isMarried" name="isMarried" formControlName="isMarried"> </p> <div *ngIf="!isMarried.valid && (isMarried.dirty ||isMarried.touched)"> <div [hidden]="!isMarried.errors.required"> isMarried is required </div> </div> <p> <label for="country">country </label> <select id="country" name="country" formControlName="country"> <option [ngValue]="c.id" *ngFor="let c of countryList"> {{c.name}} </option> </select> </p> <div *ngIf="!country.valid && (country.dirty ||country.touched)"> <div [hidden]="!country.errors.required"> country is required </div> </div> <div formGroupName="address"> <div class="form-group"> <label for="city">City</label> <input type="text" class="form-control" name="city" formControlName="city"> </div> <div *ngIf="!city.valid && (city.dirty ||city.touched)"> <div [hidden]="!city.errors.required"> city is required </div> </div> <div class="form-group"> <label for="street">Street</label> <input type="text" class="form-control" name="street" formControlName="street"> </div> <div *ngIf="!street.valid && (street.dirty ||street.touched)"> <div [hidden]="!street.errors.required"> street is required </div> </div> <div class="form-group"> <label for="pincode">Pin Code</label> <input type="text" class="form-control" name="pincode" formControlName="pincode"> </div> <div *ngIf="!pincode.valid && (pincode.dirty ||pincode.touched)"> <div [hidden]="!pincode.errors.required"> pincode is required </div> </div> </div> <p>{{contactForm.valid}} </p> <p> <button type="submit" [disabled]="!contactForm.valid">Submit</button> </p> </form> <!-- <div ngModelGroup="address"> <p> <label for="city">City</label> <input type="text" id="city" name="city"> </p> <p> <label for="street">Street</label> <input type="text" id="street" name="street"> </p> <p> <label for="pincode">Pin Code</label> <input type="text" id="pincode" name="pincode"> </p> </div> --> |
Summary
We just learned how Angular Reactive Forms Validation works.
List of All Articles on Angular Forms
- Angular Forms Tutorial: Fundamental & Concepts
- Template Driven Forms in Angular
- Set Value in Template Driven forms in Angular
- Reactive Forms in Angular
- FormBuilder in Reactive Forms
- SetValue & PatchValue in Angular
- StatusChanges in Angular Forms
- ValueChanges in Angular Forms
- FormControl
- FormGroup
- FormArray Example
- Build Dynamic or Nested Forms using FormArray
- Validations in Reactive Forms in Angular
- Custom Validator in Reactive Forms
- Passing Parameter to Custom Validator in Reactive Forms
- Inject Service into Custom Validator
- Validation in Template Driven Forms
- Custom Validator in Template Driven Forms
Thank you! It is very useful.
I assume that the author wrote this text in an old version of Angular. I googled a few syntax errors and fixed them. VS code itself helps.
This code won’t work in Angular 13. Just FYI.
Follow this Code Pattern,for Email field :
Email is required
invalid email
Thanks Team,
your code really helped me a lot.
But there is an issue in your code while fetching data and checking it for error. after error there should be question mark as you are fetching the error. It didn’t worked in angular 12. No idea about previous versions.
“” it should be like this for angular 12———– “”
it’s not working for me as well can you please tell me how can I solve the error?
I’m getting this error {Object is possibly ‘null’.ngtsc(2531)}
for this {errors.required}
Simply put {error?.required} works for me
Follow this Code Pattern,
Email is required
invalid email