Custom Validation In Template-Driven Angular Forms

This article is all about how to implement customized validations when you are working with Template-Driven approach of Angular Forms.
 
Angular provides built-in validators like required, minlength, maxlength, pattern, etc. but when we want any customized logic for the validation and that can be used anywhere in the application too, then we can use Angular Directives to serve the purpose. Inside this directive, we can describe our customized validation function.
 
First, in order to use the Template-Driven Forms, we have to import the FormsModule in our app.module.ts file as below.
  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3. import { FormsModule } from '@angular/forms';  
  4.   
  5. import { AppComponent } from './app.component';  
  6. import { LoginComponent } from './login/login.component';  
  7.   
  8. @NgModule({  
  9.   declarations: [  
  10.     AppComponent,  
  11.     LoginComponent  
  12.   ],  
  13.   imports: [  
  14.     FormsModule,  
  15.     BrowserModule,  
  16.   ],  
  17.   providers: [],  
  18.   bootstrap: [AppComponent]  
  19. })  
  20. export class AppModule { }  
Let’s implement two custom validations here - one for Email and the other one for Confirm Password.
 
Let’s begin with Email Validation first.
 

Custom Validation for Email Address

 
Now as I said, we will create a directive and name it as EmailValidator.
 
In our example, we want to validate an email address using some regex. So, the email address that is entered by the user should have a valid format according to the regex defined.
 
So, our emailvalidator.directive.ts will look like this.
  1. import { Directive } from '@angular/core';  
  2. import { Validator, NG_VALIDATORS, ValidatorFn, FormControl } from '@angular/forms';  
  3.   
  4. @Directive({  
  5.   selector: '[appEmailvalidator]',  
  6.   providers: [  
  7.     {  
  8.       provide: NG_VALIDATORS,  
  9.       useClass: EmailvalidatorDirective,  
  10.       multi: true  
  11.     }  
  12.   ]  
  13. })  
  14. export class EmailvalidatorDirective implements Validator {  
  15.   
  16.   validator: ValidatorFn;  
  17.   constructor() {  
  18.     this.validator = this.emailValidator();  
  19.   }  
  20.   
  21.   validate(c: FormControl) {  
  22.     return this.validator(c);  
  23.   }  
  24.   
  25.   emailValidator(): ValidatorFn {  
  26.     return (control: FormControl) => {  
  27.       if (control.value != null && control.value !== '') {  
  28.         let isValid = /^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/.test(control.value);  
  29.         if (isValid) {  
  30.           return null;  
  31.         } else {  
  32.           return {  
  33.             emailvalidator: { valid: false }  
  34.           };  
  35.         }  
  36.       } else {  
  37.         return null;  
  38.       }  
  39.     };  
  40.   }  
  41. }  
Okay, let’s break down the above code and understand their working.
 
Firstly, check the providers' array inside @Directive decorator.
 
The first element of this array is provide: NG_VALIDATORS. This is a special token and this will add (register) our validator to the collection of all existing validators (like required or minlength). This means that all built-in validators are already added in NG_VALIDATORS token and we are adding our own validator to this.
 
In the next element, i.e., useClass, we are providing the class name of our custom directive.
 
And the last element multi: true is a special kind of provider. This will return multiple dependencies as a list of a given token. Basically, we are adding a new value to the NG_VALIDATORS token by taking advantage of multi providers.
 
Next is our class EmailvalidatorDirective. This class is implementing ValidatorInterface. So here, we need to override the Validate() method of this interface. This validate() method accepts the instance of FormControl as its parameter. And this instance is nothing but the control which we want to validate so, in our case, it will be userEmail control.
 
The last thing is our emailValidator() method. Inside this method, we will actually define our customized logic for validation. This method will return null if the email address is in proper format otherwise it will return validation error.
 
Finally, we can apply this directive to our template (text input) like below.
  1. <input class="form-control"  
  2.                    id="userEmaild"  
  3.                    type="text"  
  4.                    appEmailvalidator  
  5.                    placeholder="User Email (required)"  
  6.                    required  
  7.                    ngModel  
  8.                    name="userEmail"  
  9.                    #userEmailVar="ngModel"  
  10.                    [ngClass]="{'is-invalid': (userEmailVar.touched || userEmailVar.dirty) && !userEmailVar.valid }" />  
Notice above that appEmailvalidator applied as an attribute to the <input> element. This will make a call to validation function when user enters the value to the input box.
 
For your convenience, below is the full code for both - Template (HTML) and TS file.
 
login.component.html (used bootstrap css) 
  1. <div class="col-md-6 col-md-offset-4">  
  2. <div class="card">  
  3.   <div class="card-header">  
  4.     <b>Log In</b>  
  5.   </div>  
  6.   
  7.   <div class="card-body">  
  8.     <form (ngSubmit)="login(loginForm)"  
  9.           #loginForm="ngForm"  
  10.           autocomplete="off">  
  11.       <fieldset>  
  12.   
  13.         <div class="form-group row">  
  14.           <label class="col-md-2 col-form-label"  
  15.                  for="userEmailId">User Email</label>  
  16.           <div class="col-md-8">  
  17.             <input class="form-control"  
  18.                    id="userEmaild"  
  19.                    type="text"  
  20.                    appEmailvalidator  
  21.                    placeholder="User Email (required)"  
  22.                    required  
  23.                    ngModel  
  24.                    name="userEmail"  
  25.                    #userEmailVar="ngModel"  
  26.                    [ngClass]="{'is-invalid': (userEmailVar.touched || userEmailVar.dirty) && !userEmailVar.valid }" />  
  27.             <span class="invalid-feedback">  
  28.               <span *ngIf="userEmailVar.hasError('required')">  
  29.                 User email is required.  
  30.               </span>  
  31.               <span *ngIf="userEmailVar.hasError('emailvalidator')">  
  32.                 Please provide a valid email address  
  33.               </span>  
  34.             </span>  
  35.           </div>  
  36.         </div>  
  37.   
  38.         <div class="form-group row">  
  39.           <label class="col-md-2 col-form--label"  
  40.                  for="passwordId">Password</label>  
  41.   
  42.           <div class="col-md-8">  
  43.             <input class="form-control"  
  44.                    id="passwordId"  
  45.                    type="password"  
  46.                    placeholder="Password (required)"  
  47.                    required  
  48.                    ngModel  
  49.                    name="password"  
  50.                    #passwordVar="ngModel"  
  51.                    [ngClass]="{'is-invalid': (passwordVar.touched || passwordVar.dirty) && !passwordVar.valid }" />  
  52.             <span class="invalid-feedback">  
  53.               <span *ngIf="passwordVar.hasError('required')">  
  54.                 Password is required.  
  55.               </span>  
  56.             </span>  
  57.           </div>  
  58.         </div>  
  59.   
  60.         <div class="row">  
  61.           <div class="col-md-4 offset-md-2">  
  62.             <button class="btn btn-primary mr-3"  
  63.                     type="submit"  
  64.                     style="width:80px"  
  65.                     [disabled]="!loginForm.valid">  
  66.               Log In  
  67.             </button>  
  68.           </div>  
  69.         </div>  
  70.       </fieldset>  
  71.     </form>  
  72.   </div>  
  73. </div>  
  74.   
  75. <div class="alert alert-danger" *ngIf="errorMessage">{{errorMessage}}</div>  
  76. </div>  
login.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2. import { NgForm } from '@angular/forms';  
  3.   
  4. @Component({  
  5.   selector: 'app-login',  
  6.   templateUrl: './login.component.html',  
  7.   styleUrls: ['./login.component.css']  
  8. })  
  9. export class LoginComponent implements OnInit {  
  10.   
  11.   errorMessage: string;  
  12.   
  13.   constructor() { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   
  18.   login(loginForm: NgForm) {  
  19.     if (loginForm && loginForm.valid) {  
  20.       const userEmail = loginForm.form.value.userEmail;  
  21.       const password = loginForm.form.value.password;  
  22.       alert('Welcome..!!');  
  23.       console.log(userEmail, password);  
  24.     } else {  
  25.       this.errorMessage = 'Please enter a user email and password.';  
  26.     }  
  27.   }  
  28. }  
Okay, so let’s check it out by running our application..!!
 
Custom Validation In Template-Driven Angular Forms
 

Custom Validation for Confirm Password

 
Let’s have another validation where we will match the password and confirm password.
 
For this, we will be adding another directive and name it as passwordvalidator.
 
This directive will match the value of the password and confirm password input fields. If both are matched then it will return null otherwise return validation error.
 
So, our passwordvalidator.directive.ts will look like this.
  1. import { Directive, Attribute } from '@angular/core';  
  2. import { NG_VALIDATORS, Validator, FormControl } from '@angular/forms';  
  3. import { Subscription } from 'rxjs';  
  4.   
  5. @Directive({  
  6.   selector: '[appPasswordvalidator]',  
  7.   providers: [  
  8.     {  
  9.       provide: NG_VALIDATORS,  
  10.       useClass: PasswordvalidatorDirective,  
  11.       multi: true  
  12.     }  
  13.   ]  
  14. })  
  15. export class PasswordvalidatorDirective implements Validator {  
  16.   
  17.   constructor(@Attribute('appPasswordvalidator'public PasswordControl: string) { }  
  18.   
  19.   validate(c: FormControl) {  
  20.   
  21.     const Password = c.root.get(this.PasswordControl);  
  22.     const ConfirmPassword = c;  
  23.   
  24.     if (ConfirmPassword.value === null) {  
  25.       return null;  
  26.     }  
  27.   
  28.     if (Password) {  
  29.       const subscription: Subscription = Password.valueChanges.subscribe(() => {  
  30.         ConfirmPassword.updateValueAndValidity();  
  31.         subscription.unsubscribe();  
  32.       });  
  33.     }  
  34.     return Password && Password.value !== ConfirmPassword.value ? { passwordMatchError: true } : null;  
  35.   }  
  36.   
  37. }  
As you can see, this directive is also having the same structure or format as the EmailValidator directive is having. A couple of differences are the method() where we are specifying the logic how both passwords can be matched and another one is in the constructor where we are having a parameter for @Attribute.
 
Let’s see why we used @Attribute decorator:
 
The @Attribute decorator returns the value of the specified attribute from the host. We are injecting attribute value via this decorator and assign it to the passwordControl variable. In our case host is the input box of the password so the value of the passwordControl variable would be the value of the password input box.
 
Okay, let’s move on and understand the validate() method.
 
First, we find the value of password input control in our form and assigned to the Password variable. Then, we read the value of our input (on which this directive is applied) and assign it to the ConfirmPassword variable. 
  1. const Password = c.root.get(this.PasswordControl);  
  2. const ConfirmPassword = c;  
After that, we get the latest value of Password whenever there is any change. This can be done by valueChanges(). More precisely we are subscribing to valueChanges to get the real-time value.
  1. if (Password) {  
  2.       const subscription: Subscription = Password.valueChanges.subscribe(() => {  
  3.         ConfirmPassword.updateValueAndValidity();  
  4.         subscription.unsubscribe();  
  5.       });  
  6. }  
Inside this Subscribe method we are using updateValueAndValidity() on ConfirmPassword. This method will recalculate the value and validations of ConfirmPassword every time when there is a change in Password.
 
Then, we are using unsubscribe() on our Password. This will destroy the data that has been previously held by Password when we have applied subscribe() on it.
 
Now, the last important thing is to compare Password and ConfirmPassword values.
 
return Password && Password.value !== ConfirmPassword.value ? { passwordMatchError: true } : null;
 
So here, we are checking both values are equal or not and returns validation error if they are not equal.
 
Finally, we can apply this directive to our template.
 
In our HTML, now we need to add only one more input box for Confirm Password. 
  1. <input class="form-control"  
  2.                  id="CnfrmPasswordId"  
  3.                  type="password"  
  4.                  placeholder="Confirm Password (required)"  
  5.                  required  
  6.                  appPasswordvalidator = "passwordVar"  
  7.                  ngModel  
  8.                  name="CnfmPasswordVar"  
  9.                  #CnfmPasswordVar="ngModel"  
  10.                  [ngClass]="{'is-invalid': (CnfmPasswordVar.touched || CnfmPasswordVar.dirty) && !CnfmPasswordVar.valid }" />  
Notice above that we have applied appPasswordvalidator as an attribute to the <input> element and we are assigning Password input control to it. By assigning this value, we are able to get it in @Attribute variable in the directive’s constructor.
 
Let’s run the application and check it out..!!
 
Custom Validation In Template-Driven Angular Forms
Custom Validation In Template-Driven Angular Forms
 
 
Thanks for reading. Happy Coding..!!