Angular Conditional Validation
July 27, 2018
This page will walk through Angular conditional validation tutorial. We perform conditional validation using valueChanges
property or registerOnValidatorChange()
method. We subscribe valueChanges
of a form control to set and clear validators on any field in reactive form and we call updateValueAndValidity()
to recalculate the value and validation status of the control. Using valueChanges
we can perform conditional validation for built-in and custom validators both in reactive form. We can create custom validator functions using Angular ValidatorFn
and AsyncValidatorFn
interfaces and we can create custom validator Directive using Angular Validator
and AsyncValidator
interfaces. Validator Directive can be used with formControlName
, formControl
and ngModel
. To perform conditional validation using registerOnValidatorChange()
we need to create custom validator Directive and define this method. registerOnValidatorChange()
is the method of Validator
and AsyncValidator
interface both. registerOnValidatorChange()
registers a function that can be executed conditionally to validate a field. Some built-in validator Directives such as required
, email
can be conditionally enabled and disabled by assigning boolean values using property binding. Here on this page we will provide complete example to perform conditional validation using valueChanges
property and updateValueAndValidity()
method for reactive form and we will use registerOnValidatorChange()
method for template-driven form.
Contents
- Technologies Used
- Project Structure
- Conditional Custom Validation with valueChanges and updateValueAndValidity()
- Conditional Custom Validation with registerOnValidatorChange()
- Conditional Validation with Built-in Validators
- Example-1: Reactive Form Conditional Validation
- Example-2: Template-Driven Form Conditional Validation
- Run Application
- References
- Download Source Code
Technologies Used
Find the technologies being used in our example.1. Angular 6.0.3
2. Angular CLI 6.0.3
3. TypeScript 2.7.2
4. Node.js 10.3.0
5. NPM 6.1.0
Project Structure
Find the project structure of our demo application.my-app | |--src | | | |--app | | | | | |--custom-validators | | | | | | | |--confirm-password-validator.ts | | | |--pwd-match-username-validator.ts | | | | | |--reactive-form.component.ts | | |--reactive-form.component.html | | |--template-driven-form.component.ts | | |--template-driven-form.component.html | | |--user.ts | | | | | |--app.component.ts | | |--app.module.ts | | | |--main.ts | |--index.html | |--styles.css | |--node_modules |--package.json
Conditional Custom Validation with valueChanges and updateValueAndValidity()
valueChanges
and updateValueAndValidity()
are used for conditional validation in reactive form. valueChanges
is a property of AbstractControl
that emits an event every time when the value of control changes either using UI or programmatically. updateValueAndValidity()
is the method of AbstractControl
that recalculates the value and validation status of the control. valueChanges
and updateValueAndValidity()
are available in FormControl
, FormArray
and FormGroup
classes because they inherit AbstractControl
class. valueChanges
property has been declared as following.
get valueChanges: Observable<any>
Observable
instance. We can subscribe valueChanges
to get data. Here we will perform conditional custom validation by subscribing valueChanges
of a FormControl
.
Now create a reactive user form.
this.userForm = this.formBuilder.group({ username: ['', [ Validators.required ]], password: ['', [ Validators.required ]], confirmPassword: ['', [ Validators.required ]], notificationMode: ['', [ Validators.required ]], email: '', mobileNumber: '' });
Suppose we want conditional validation that username and password cannot be same. First of all create a custom validator using
ValidatorFn
.
pwd-match-username-validator.ts
import { AbstractControl, ValidatorFn } from '@angular/forms'; export function pwdMatchUsernameValidator(username: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password === username) ? true : false; return isInValid ? {'matchForUsername': {value: 'Invalid'}} : null; }; }
Find the getter properties for
username
and password
as following.
get username() { return this.userForm.get('username'); } get password() { return this.userForm.get('password'); }
valueChanges
. By subscribing valueChanges
of username
and password
getter properties, we will set its Validators
.
handleFormChanges() { this.username.valueChanges.subscribe( uname => { this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.password.updateValueAndValidity(); } ); this.password.valueChanges.subscribe( pwd => { const uname = this.username.value; this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); } ); }
setValidators()
we set synchronous validators and using setAsyncValidators()
we set asynchronous validators. Whenever we change value in password field, the password valueChanges
subscribe block will execute. Our custom validator pwdMatchUsernameValidator()
will execute with current username field value and the password field will be validated.
If password is validated against username and then we change username value, then password field must be revalidated. To revalidate password on change of username, we need to go to password
valueChanges
subscribe block and set validator for password and then call updateValueAndValidity()
on password property as following.
this.password.updateValueAndValidity();
Finally to execute
handleFormChanges()
method we need to call it inside ngOnInit()
method.
ngOnInit() { this.handleFormChanges(); }
Suppose we want to validate password and confirm password fields to be same. We will create a custom validator using
ValidatorFn
as following.
confirm-password-validator.ts
import { AbstractControl, ValidatorFn } from '@angular/forms'; export function confirmPasswordValidator(confirmPassword: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password !== confirmPassword) ? true : false; return isInValid ? {'cnfPassword': {value: 'Invalid'}} : null; }; }
get confirmPassword() { return this.userForm.get('confirmPassword'); }
valueChanges
property for username, password and confirm password fields.
handleFormChanges() { this.username.valueChanges.subscribe( uname => { this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.password.updateValueAndValidity(); } ); this.password.valueChanges.subscribe( pwd => { const uname = this.username.value; this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); this.confirmPassword.updateValueAndValidity(); } ); this.confirmPassword.valueChanges.subscribe( () => { const pwd = this.password.value; this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); } ); }
Our HTML template code snippet of reactive form will be as following.
<tr> <td>Username: *</td> <td> <input formControlName="username"> <div *ngIf="username.dirty && username.errors" class = "error"> <div *ngIf="username.errors.required"> Username required. </div> </div> </td> </tr> <tr> <td>Password: *</td> <td> <input formControlName="password" type="password"> <div *ngIf="password.dirty && password.errors" class = "error"> <div *ngIf="password.errors.required"> Password required. </div> <div *ngIf="password.errors.matchForUsername"> Password cannot be same as Username. </div> </div> </td> </tr> <tr> <td>Confirm Password: *</td> <td> <input formControlName="confirmPassword" type="password"> <div *ngIf="confirmPassword.dirty && confirmPassword.errors" class = "error"> <div *ngIf="confirmPassword.errors.required"> Confirm Password required. </div> <div *ngIf="confirmPassword.errors.cnfPassword"> Confirm Password not matched. </div> </div> </td> </tr>
Conditional Custom Validation with registerOnValidatorChange()
To use conditional custom validation withformControlName
, formControl
and ngModel
we need to create custom validator Directive using Validator
or AsyncValidator
interface. Validator
creates synchronous custom validator and AsyncValidator
creates asynchronous custom validator. Validator
and AsyncValidator
has following method declarations.
validate(): It validates the value.
registerOnValidatorChange(): It registers a function that can be called to revalidate the value.
Let us understand how to register a function using
registerOnValidatorChange()
.
Declare a function as following.
private _onChange: () => void;
registerOnValidatorChange()
.
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
_onChange()
, it will execute validate()
method to revalidate value. Using ngOnChanges()
we can track the changes in value of @Input
properties of our validator Directive and conditionally we can call our registered function _onChange()
. In this way we can achieve conditional validation.
1. Username and Password must not match validation
We will create a validator Directive for conditional custom validation that password cannot match username.
pwd-match-username-validator.ts
import { Directive, Input, OnChanges , SimpleChanges } from '@angular/core'; import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '@angular/forms'; export function pwdMatchUsernameValidator(username: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password === username) ? true : false; return isInValid ? {'matchForUsername': {value: 'Invalid'}} : null; }; } @Directive({ selector: '[matchForUsername][formControlName],[matchForUsername][formControl],[matchForUsername][ngModel]', providers: [{provide: NG_VALIDATORS, useExisting: PwdMatchUsernameValidatorDirective, multi: true}] }) export class PwdMatchUsernameValidatorDirective implements Validator, OnChanges { @Input('matchForUsername') changedUsername: string; private _onChange: () => void; validate(control: AbstractControl): {[key: string]: any} | null { return this.changedUsername ? pwdMatchUsernameValidator(this.changedUsername)(control): null; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } ngOnChanges(changes: SimpleChanges) { if ('changedUsername' in changes) { if (this._onChange) this._onChange(); } } }
ngOnChanges()
method that executes when any data-bound property of the Directive changes. For any change in changedUsername
property of Directive, we are executing _onChange()
function to revalidate password.
2. Password and Confirm Password must match validation
In the same way as above if we want conditional custom validation to ensure that password and confirm password must match, we can create custom validator Directive as following.
confirm-password-validator.ts
import { Directive, Input, OnChanges , SimpleChanges } from '@angular/core'; import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '@angular/forms'; export function confirmPasswordValidator(confirmPassword: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password !== confirmPassword) ? true : false; return isInValid ? {'cnfPassword': {value: 'Invalid'}} : null; }; } @Directive({ selector: '[cnfPassword][formControlName],[cnfPassword][formControl],[cnfPassword][ngModel]', providers: [{provide: NG_VALIDATORS, useExisting: ConfirmPasswordValidatorDirective, multi: true}] }) export class ConfirmPasswordValidatorDirective implements Validator, OnChanges { @Input('cnfPassword') confirmPassword: string; private _onChange: () => void; validate(control: AbstractControl): {[key: string]: any} | null { return this.confirmPassword ? confirmPasswordValidator(this.confirmPassword)(control): null; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } ngOnChanges(changes: SimpleChanges) { if ('confirmPassword' in changes) { if (this._onChange) this._onChange(); } } }
ngOnChanges()
method, we are executing _onChange()
function to revalidate confirm password for any change in password.
We need to configure custom validator Directive in application module within
declarations
block of @NgModule
as following.
app.module.ts
import { PwdMatchUsernameValidatorDirective } from './custom-validators/pwd-match-username-validator'; import { ConfirmPasswordValidatorDirective } from './custom-validators/confirm-password-validator'; @NgModule({ declarations: [ ------ PwdMatchUsernameValidatorDirective, ConfirmPasswordValidatorDirective ], ------ }) export class AppModule { }
formControlName
, formControl
and ngModel
. Find the HTML template code snippet for template-driven form with our validator Directive.
<tr> <td>Username: *</td> <td> <input name="username" required ngModel #uname="ngModel"> <div *ngIf="uname.dirty && uname.errors" class = "error"> <div *ngIf="uname.errors.required"> Username required. </div> </div> </td> </tr> <tr> <td>Password: *</td> <td> <input type="password" name="password" required [matchForUsername]="uname.value" ngModel #pwd="ngModel"> <div *ngIf="pwd.dirty && pwd.errors" class = "error"> <div *ngIf="pwd.errors.required"> Password required. </div> <div *ngIf="pwd.errors.matchForUsername"> Password cannot be same as Username. </div> </div> </td> </tr> <tr> <td>Confirm Password: *</td> <td> <input type="password" name="confirmPassword" required [cnfPassword]="pwd.value" ngModel #cnfPwd="ngModel"> <div *ngIf="cnfPwd.dirty && cnfPwd.errors" class = "error"> <div *ngIf="cnfPwd.errors.required"> Confirm Password required. </div> <div *ngIf="cnfPwd.errors.cnfPassword"> Confirm Password not matched. </div> </div> </td> </tr>
Conditional Validation with Built-in Validators
Here we will use built-in validators such asrequired
, email
for conditional validations. Suppose we have three notification mode i.e. email, mobile number and both and we want that if email is selected then validation should be applied only on email field, if mobile number is selected then validation should be applied only on mobile number field, if both option is selected then validation should be applied on both fields email and mobile number. To achieve this conditional validation we can use valueChanges
or expressions that returns boolean value.
1. Using
valueChanges
and updateValueAndValidity()
valueChanges
will be helpful in reactive form. Suppose we have getter properties for notification mode, email and mobile number as following.
get notificationMode() { return this.userForm.get('notificationMode'); } get email() { return this.userForm.get('email'); } get mobileNumber() { return this.userForm.get('mobileNumber'); }
valueChanges
of notificationMode
property and set and clear validators as following.
handleFormChanges() { this.notificationMode.valueChanges.subscribe( mode => { if (mode==='email') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.clearValidators(); } else if (mode === 'mobile') { this.mobileNumber.setValidators([Validators.required]); this.email.clearValidators(); } else if (mode==='both') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.setValidators([Validators.required]); } this.email.updateValueAndValidity(); this.mobileNumber.updateValueAndValidity(); } ); }
To perform conditional validations with
formControlName
, formControl
and ngModel
using built-in validators, we can use build-in validator Directives. For example if we write [required]="false"
then required validation will not work and if we write [required]="true"
then required validation will work. Find the code snippet to perform conditional validation in template-driven form.
<tr> <td>Notification Mode: *</td> <td> <input type="radio" value="email" name="notificationMode" required ngModel #mode="ngModel"> Email <input type="radio" value="mobile" name="notificationMode" required ngModel #mode="ngModel"> Mobile <input type="radio" value="both" name="notificationMode" required ngModel #mode="ngModel"> Both </td> </tr> <tr> <td>Email: </td> <td> <input name="email" [required]="mode.value === 'email' || mode.value === 'both'" [email]= "mode.value === 'email' || mode.value === 'both'" ngModel #uemail="ngModel"> <div *ngIf="uemail.dirty && uemail.errors" class = "error"> <div *ngIf="uemail.errors.required"> Email required. </div> <div *ngIf="uemail.errors.email"> Email not valid. </div> </div> </td> </tr> <tr> <td>Mobile Number: </td> <td> <input name="mobileNumber" [required]="mode.value === 'mobile' || mode.value === 'both'" ngModel #mnumber="ngModel"> <div *ngIf="mnumber.dirty && mnumber.errors" class = "error"> <div *ngIf="mnumber.errors.required"> Mobile number required. </div> </div> </td> </tr>
Example-1: Reactive Form Conditional Validation
Here we will provide complete code to perform reactive form conditional validation usingvalueChanges
property and updateValueAndValidity()
method for custom and built-in validators.
reactive-form.component.ts
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; import { User } from './user'; import { pwdMatchUsernameValidator } from './custom-validators/pwd-match-username-validator'; import { confirmPasswordValidator } from './custom-validators/confirm-password-validator'; @Component({ selector: 'app-reactive-form', templateUrl: './reactive-form.component.html' }) export class ReactiveFormComponent implements OnInit { userForm: FormGroup; constructor(private formBuilder:FormBuilder) { } ngOnInit() { this.userForm = this.formBuilder.group({ username: ['', [ Validators.required ]], password: ['', [ Validators.required ]], confirmPassword: ['', [ Validators.required ]], notificationMode: ['', [ Validators.required ]], email: '', mobileNumber: '' }); this.handleFormChanges(); } handleFormChanges() { this.username.valueChanges.subscribe( uname => { this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.password.updateValueAndValidity(); } ); this.password.valueChanges.subscribe( pwd => { const uname = this.username.value; this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); this.confirmPassword.updateValueAndValidity(); } ); this.confirmPassword.valueChanges.subscribe( () => { const pwd = this.password.value; this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); } ); this.notificationMode.valueChanges.subscribe( mode => { if (mode==='email') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.clearValidators(); } else if (mode === 'mobile') { this.mobileNumber.setValidators([Validators.required]); this.email.clearValidators(); } else if (mode==='both') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.setValidators([Validators.required]); } this.email.updateValueAndValidity(); this.mobileNumber.updateValueAndValidity(); } ); } onFormSubmit() { let user: User = this.userForm.value; console.log(user); this.userForm.reset(); } get username() { return this.userForm.get('username'); } get password() { return this.userForm.get('password'); } get confirmPassword() { return this.userForm.get('confirmPassword'); } get notificationMode() { return this.userForm.get('notificationMode'); } get email() { return this.userForm.get('email'); } get mobileNumber() { return this.userForm.get('mobileNumber'); } }
<h3>Reactive User Form</h3> <form [formGroup]="userForm" (ngSubmit)="onFormSubmit()"> <table> <tr> <td>Username: *</td> <td> <input formControlName="username"> <div *ngIf="username.dirty && username.errors" class = "error"> <div *ngIf="username.errors.required"> Username required. </div> </div> </td> </tr> <tr> <td>Password: *</td> <td> <input formControlName="password" type="password"> <div *ngIf="password.dirty && password.errors" class = "error"> <div *ngIf="password.errors.required"> Password required. </div> <div *ngIf="password.errors.matchForUsername"> Password cannot be same as Username. </div> </div> </td> </tr> <tr> <td>Confirm Password: *</td> <td> <input formControlName="confirmPassword" type="password"> <div *ngIf="confirmPassword.dirty && confirmPassword.errors" class = "error"> <div *ngIf="confirmPassword.errors.required"> Confirm Password required. </div> <div *ngIf="confirmPassword.errors.cnfPassword"> Confirm Password not matched. </div> </div> </td> </tr> <tr> <td>Notification Mode: *</td> <td> <input type="radio" value="email" formControlName="notificationMode"> Email <input type="radio" value="mobile" formControlName="notificationMode"> Mobile <input type="radio" value="both" formControlName="notificationMode"> Both </td> </tr> <tr> <td>Email: </td> <td> <input formControlName="email"> <div *ngIf="email.dirty && email.errors" class = "error"> <div *ngIf="email.errors.required"> Email required. </div> <div *ngIf="email.errors.email"> Email not valid. </div> </div> </td> </tr> <tr> <td>Mobile Number: </td> <td> <input formControlName="mobileNumber"> <div *ngIf="mobileNumber.dirty && mobileNumber.errors" class = "error"> <div *ngIf="mobileNumber.errors.required"> Mobile number required. </div> </div> </td> </tr> <tr> <td colspan="2"> <button [disabled]="userForm.invalid">Submit</button> </td> </tr> </table> </form>
export class User { username: string; password: string; notificationMode: string; email: string; mobileNumber: string; }
Example-2: Template-Driven Form Conditional Validation
Here we will provide complete code to perform template-driven form conditional validation using custom and built-in validator Directive. We have already created our custom validator Directive above in the article for conditional validation for username and password and confirm password i.e.matchForUsername
and cnfPassword
. Here we will use them. Find the complete template-driven user form code.
template-driven-form.component.ts
import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; import { User } from './user'; import { Observable } from 'rxjs'; @Component({ selector: 'app-template-form', templateUrl: './template-driven-form.component.html' }) export class TemplateDrivenFormComponent implements OnInit { constructor() { } ngOnInit() { } onFormSubmit(form: NgForm) { let user: User = form.value; console.log(user); form.resetForm(); } }
<h3>Template-driven User Form</h3> <form #userForm="ngForm" (ngSubmit)="onFormSubmit(userForm)"> <table> <tr> <td>Username: *</td> <td> <input name="username" required ngModel #uname="ngModel"> <div *ngIf="uname.dirty && uname.errors" class = "error"> <div *ngIf="uname.errors.required"> Username required. </div> </div> </td> </tr> <tr> <td>Password: *</td> <td> <input type="password" name="password" required [matchForUsername]="uname.value" ngModel #pwd="ngModel"> <div *ngIf="pwd.dirty && pwd.errors" class = "error"> <div *ngIf="pwd.errors.required"> Password required. </div> <div *ngIf="pwd.errors.matchForUsername"> Password cannot be same as Username. </div> </div> </td> </tr> <tr> <td>Confirm Password: *</td> <td> <input type="password" name="confirmPassword" required [cnfPassword]="pwd.value" ngModel #cnfPwd="ngModel"> <div *ngIf="cnfPwd.dirty && cnfPwd.errors" class = "error"> <div *ngIf="cnfPwd.errors.required"> Confirm Password required. </div> <div *ngIf="cnfPwd.errors.cnfPassword"> Confirm Password not matched. </div> </div> </td> </tr> <tr> <td>Notification Mode: *</td> <td> <input type="radio" value="email" name="notificationMode" required ngModel #mode="ngModel"> Email <input type="radio" value="mobile" name="notificationMode" required ngModel #mode="ngModel"> Mobile <input type="radio" value="both" name="notificationMode" required ngModel #mode="ngModel"> Both </td> </tr> <tr> <td>Email: </td> <td> <input name="email" [required]="mode.value === 'email' || mode.value === 'both'" [email]= "mode.value === 'email' || mode.value === 'both'" ngModel #uemail="ngModel"> <div *ngIf="uemail.dirty && uemail.errors" class = "error"> <div *ngIf="uemail.errors.required"> Email required. </div> <div *ngIf="uemail.errors.email"> Email not valid. </div> </div> </td> </tr> <tr> <td>Mobile Number: </td> <td> <input name="mobileNumber" [required]="mode.value === 'mobile' || mode.value === 'both'" ngModel #mnumber="ngModel"> <div *ngIf="mnumber.dirty && mnumber.errors" class = "error"> <div *ngIf="mnumber.errors.required"> Mobile number required. </div> </div> </td> </tr> <tr> <td colspan="2"> <button [disabled]="userForm.invalid">Submit</button> </td> </tr> </table> </form>
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-reactive-form></app-reactive-form> <app-template-form></app-template-form> ` }) export class AppComponent { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { ReactiveFormComponent } from './reactive-form.component'; import { TemplateDrivenFormComponent } from './template-driven-form.component'; import { PwdMatchUsernameValidatorDirective } from './custom-validators/pwd-match-username-validator'; import { ConfirmPasswordValidatorDirective } from './custom-validators/confirm-password-validator'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule, FormsModule ], declarations: [ AppComponent, ReactiveFormComponent, TemplateDrivenFormComponent, PwdMatchUsernameValidatorDirective, ConfirmPasswordValidatorDirective ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { }
table { border-collapse: collapse; } table, th, td { border: 1px solid black; } .error { color: red; } .success { color: green; }
Run Application
To run the application, find the steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Access the URL http://localhost:4200
Enter the data into user form and if form is not validated, we will get error messages. Find the print screen.

References
Angular Doc: valueChangesAngular Doc: updateValueAndValidity()
Angular Doc: registerOnValidatorChange()
Angular valueChanges and statusChanges