Home » Framework » Angular » Form validation using custom validator in Angular Application

Form validation using custom validator in Angular Application

Here let us see what is custom validator? how to create a custom validator? how to validate template-driven and reactive forms using a custom validator in angular application. We need a custom validator when the built-validator is not sufficient to validate the form fields.

In the last article, we have already seen the built-in validators and how to use those to validate both the template-driven and reactive forms. If you want to know that please take a look at Form Validation in Angular using built-in validators.

What is Custom Validator?

Custom validator is a function to create the validator function to validate the form fields. We can say it's a function and it returns a function of type ValidatorFn.

How to create Custom Validator?

  1. Custom validator function needs at least one argument that is of type AbstractControl. This validator function gets the field value through the property called control.value and validates that.
  2. This validator function needs to return the null if the field value is valid and no errors are found.
  3. It returns the error object of type ValidationErrors if any errors are found or a field value is invalid.
  4. This ValidationErrors object will be a single field that has a single error message or multiple fields that has different error messages or nested object

Here let us create the sign-up up form (template-driven & reactive) with a mix of both built-in validators and custom validators. The fields are the form is below.

Form FieldsValidators Used
firstNamebuilt-in validator required and minlength.
lastNamebuilt-in validator required and maxlength.
emailbuilt-in validator required and email.
passwordCustom Validator to validate the strength of the password

Now let's focus on how to create the custom validator to validate the strength of the password.

Validate Reactive Form using Custom Validator

It's very simple to add a custom validator in reactive forms. We have to create the custom validator function and put that function directly in the component class in the Validators section of form controls.

Let us create the reactive sign-up form with the fields mentioned above.

app.component.html

<h2>Reactice Sign-up Form with built-in & custom validators </h2> 
<form [formGroup]="signupForm" (ngSubmit)="ReSignupFormSubmit()" novalidate>
    <div class="signup-form">
        <div class="signup-form-controls">
            <label>First Name</label>
            <input type="text" formControlName="firstName" name="firstname">
            <div *ngIf="signupForm.controls.firstName.invalid && 
                (signupForm.controls.firstName.dirty || signupForm.controls.firstName.touched)" class="alert">
                <div *ngIf="signupForm.controls.firstName.errors?.required">
                    First Name is required.
                </div>   
                <div *ngIf="signupForm.controls.firstName.errors?.minlength">
                    First Name should be atleast 4 characters
                </div>   
            </div> 
        </div>
        <div class="signup-form-controls">
            <label>Last Name</label>
            <input type="text" formControlName="lastName" name="lastname" >
            <div *ngIf="signupForm.controls.lastName.invalid && 
                (signupForm.controls.lastName.dirty || signupForm.controls.lastName.touched)" class="alert">
                <div *ngIf="signupForm.controls.lastName.errors?.required">
                    Last Name is required.
                </div>   
                <div *ngIf="signupForm.controls.lastName.errors?.maxlength">
                    Last Name should be maximum of 4 characters
                </div>   
            </div> 
        </div>        
        <div class="signup-form-controls">   
            <label>Email</label>
                <input type="text" formControlName="email" name="email" >
                <div *ngIf="signupForm.controls.email.invalid && 
                    (signupForm.controls.email.dirty || signupForm.controls.email.touched)" class="alert">
                    <div *ngIf="signupForm.controls.email.errors?.required">
                        Email is required.
                    </div>
                    <div *ngIf="signupForm.controls.email.errors?.email">
                        Invalid Email Id
                    </div>       
                </div> 
        </div>         
          <div class="signup-form-controls">
              <label>Enter your password</label>
              <input type="password" formControlName="password" name="password" >
              <div *ngIf="signupForm.controls.password.invalid && 
                  (signupForm.controls.password.dirty || signupForm.controls.password.touched)" class="alert">
                  <div *ngIf="signupForm.controls.password.errors?.required">
                      Password is required.
                  </div> 
                  <div *ngIf="(signupForm.controls.password.errors?.passwordStrength || 
                  signupForm.controls.password.errors?.minlength)">
                  Your password must have atleaset one lower case, one upper case and one numeric character. Password should be minimum of 6 characters.
                  </div>                    
              </div> 
          </div>
          <div class="signup-form-controls">   
            <label>Terms and Conditions</label> 
            <input type="checkbox" formControlName="tc" name="tc">
            <div *ngIf="signupForm.controls.tc.invalid && 
                (signupForm.controls.tc.dirty || signupForm.controls.tc.touched)" class="alert">
                <div *ngIf="signupForm.controls.tc.errors?.required">
                    Tick this box
                </div>                   
            </div> 
          </div>
        <div class="signup-form-controls">
            <button type="submit" [disabled]="!signupForm.valid">Submit</button>
        </div>
    </div>
  </form>

app.component.ts

import { Component } from '@angular/core';
import { FormBuilder, Validators, AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-form-custom-validation';
  //Reactive Form Controls
  constructor(private fb: FormBuilder) { }
  signupForm = this.fb.group({
    "firstName": ["", [Validators.required, Validators.minLength(4)]],
    "lastName": ["", [Validators.required, Validators.maxLength(4)]],    
    "email": ["", [Validators.required,Validators.email]],
    "password":["", [Validators.required, Validators.minLength(6),  validatePasswordStrength()]],
    "tc": [false, Validators.requiredTrue]
  });
  ReSignupFormSubmit(){
    console.log(this.signupForm.value);
  }
}
export function validatePasswordStrength(): ValidatorFn {
  return (control:AbstractControl) : ValidationErrors | null => {
      const value = control.value;
      if (!value) {
          return null;
      }
      const upperCaseCheck = /[A-Z]+/.test(value);
      const lowerCaseCheck = /[a-z]+/.test(value);
      const numericCheck = /[0-9]+/.test(value);
      const validPassword = upperCaseCheck && lowerCaseCheck && numericCheck;
      return !validPassword ? {passwordStrength:true}: null;
  }
}

Using the reactive form in angular we need to import ReactiveFormsModule like below.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ReactiveFormsModule} from '@angular/forms';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add these below styles to make a form good-looking

app.component.css

.alert{
    color:red;
}
.signup-form{
    padding:15px;
}
.signup-form-controls{
    padding-top:20px;
}

The custom validator function validatePasswordStrength() used in the component class checks whether the password has at least one lower case, one upper case, and one numeric character or not. If all the checks are successful then it makes the password a valid password.

The built-in validators Validators.required and Validators.minLength(6) used in the password field makes sure that the field is required and it should have at least 6 characters.

Now let's run the application and see the below reactive sign-up form in the browser

Reactive-form-using-custom-validator-in-angular
Reactive-form-using-custom-validator-in-angular

Let's see how the custom validator validatePasswordStrength() works.

  1. It checks whether the password field has at least one lower case, one upper case, and one numeric character. If all the checks are successful then the password is valid.
  2. It returns null If the password is valid and there are no errors. If the password is not valid and found the errors then it returns the error object like {passwordStrength: true}.
  3. We have to check this error object {passwordStrength: true} in the form template and display the error message in the UI like below.
<div *ngIf="(signupForm.controls.password.errors?.passwordStrength || 
                     signupForm.controls.password.errors?.minlength)">
   Your password must have atleaset one lower case, one upper case and one numeric character. Password should be minimum of 6 characters.
</div>

Here in the custom validator function, we have returned the error object that has a single boolean field. But we can also return the error object that has multiple fields with different error messages. We can return nested error objects also.

Validate Template-Driven Form using Custom Validator

In the template-driven form, we have to do some extra configuration to use a custom validator. Normally here the validator directives are directly used in the HTML template itself and that validator directives match with the corresponding validator functions to validate the form fields.

For example, in the built-in validator, the validator directive required mentioned in the input field match with the validator function Validators.required to validate the field. Right!. the same thing we have to configure for the custom validator also.

First, we have to create the custom validator directive like below and put the directive name in the input field to validate.

Let's create the custom validator directive by using the below command.

ng g directive PasswordStrengthDirective

It creates the two files under the app folder named 'password-strength-directive.directive.spec.ts' and 'password-strength-directive.directive.ts'

Now we have created just a directive. If we want to make that directive a custom validator directive then we have to implement the interface called Validator. This Validator interface has the method called validate.

Within this validate method we have to call the validator function validatePasswordStrength() that we created already. This custom validator function validatePasswordStrength() validate the password field. That's it.

Finally don't forget to add the custom validator directive 'appPasswordStrengthDirective' in the password input field of the form.

Now let's do the below changes in the corresponding files to create the template-driven sign-up form using the custom validator.

password-strength-directive.directive.ts

import { Directive } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
import { validatePasswordStrength } from './app.component';
@Directive({
  selector: '[appPasswordStrengthDirective]',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting:PasswordStrengthDirectiveDirective,
    multi: true
}]
})
export class PasswordStrengthDirectiveDirective implements Validator{
  constructor() { }
  validate(control: AbstractControl): ValidationErrors | null {
    return validatePasswordStrength()(control);
}
}

app.component.html

<h2>Template-Driven Sign-up Form with built-in & custom validators</h2>
<form #tdSignupForm="ngForm" (ngSubmit)="tdSignupFormSubmit()" novalidate>
    <div class="signup-form">
    <div class="signup-form-controls">
        <label>First Name</label>
        <input type="text" [(ngModel)]="user.firstName" name="firstName"  #firstName="ngModel" minlength="4" required>
        <div *ngIf="firstName.invalid && (firstName.dirty || firstName.touched)" class="alert">
            <div *ngIf="firstName.errors?.required">
                First Name is required.
            </div>   
            <div *ngIf="firstName.errors?.minlength">
                First Name should be atleast 4 characters
            </div>   
        </div>        
    </div>   
    <div class="signup-form-controls">
        <label>Last Name</label>
        <input type="text" [(ngModel)]="user.lastName" name="lastName" #lastName="ngModel" maxlength="4" required>
        <div *ngIf="lastName.invalid && (lastName.dirty || lastName.touched)" class="alert">
            <div *ngIf="lastName.errors?.required">
                Last Name is required.
            </div> 
            <div *ngIf="lastName.errors?.maxlength">
                Last Name should be maximum 4 characters
            </div>      
        </div>  
    </div>    
    <div class="signup-form-controls">   
        <label>Email</label>
        <input type="text" [(ngModel)]="user.email" name="email" #email="ngModel" required email>
        <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert">
            <div *ngIf="email.errors?.required">
                Email is required.
            </div>
            <div *ngIf="email.errors?.email">
                Invalid Email Id
            </div>       
        </div> 
    </div>   
    <div class="signup-form-controls">
      <label>Password</label>
      <input type="password" [(ngModel)]="user.password" name="password"  #password="ngModel" minlength="6" required appPasswordStrengthDirective>
      <div *ngIf="password.invalid && (password.dirty || password.touched)" class="alert">
          <div *ngIf="password.errors?.required">
              Password is required.
          </div>   
          <div *ngIf="(password.errors?.passwordStrength || password.errors?.minlength)">
              Your password must have atleaset one lower case, one upper case and one numeric character. Password should be minimum of 6 characters.
          </div>
      </div>        
  </div>
    <div class="signup-form-controls">   
        <label>Terms and Conditions</label>
        <input type="checkbox" [(ngModel)]="user.tc" name="tc" #tc="ngModel"  required>
        <div *ngIf="tc.invalid && (tc.dirty || tc.touched)" class="alert">
            <div *ngIf="tc.errors?.required">
                Tick this box
            </div>                   
        </div> 
    </div>
    <div class="signup-form-controls">
        <button type="submit" [disabled]="!tdSignupForm.valid">Submit</button>
    </div>
    </div>
</form>

app.component.ts

import { Component } from '@angular/core';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-form-custom-validation';  
  //Template-driven Form Controls
  user = {
    'firstName':'',   
    'lastName':'', 
    'email':'',
    'password':'',
    'tc':false
  }
  tdSignupFormSubmit(){
    console.log(this.user);
  }
}
export function validatePasswordStrength(): ValidatorFn {
  return (control:AbstractControl) : ValidationErrors | null => {
      const value = control.value;
      if (!value) {        
          return null;          
      }
      const upperCaseCheck = /[A-Z]+/.test(value);
      const lowerCaseCheck = /[a-z]+/.test(value);
      const numericCheck = /[0-9]+/.test(value);
      const validPassword = upperCaseCheck && lowerCaseCheck && numericCheck;
      return !validPassword ? {passwordStrength:true}: null;
  }
}

To use the template-driven form in angular we have to import the FormsModule like below.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
import { PasswordStrengthDirectiveDirective } from './password-strength-directive.directive';

@NgModule({
  declarations: [
    AppComponent,
    PasswordStrengthDirectiveDirective
  ],
  imports: [
    BrowserModule,    
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now save all the changes and run the application to see the below template-driven sign-up form in the browser.

template-driven-form-using-custom-validator
template-driven-form-using-custom-validator

I hope you understood how to validate the template-driven and reactive forms using a custom validator. Thanks!. Keep Reading!.

Download the full source code from GitHub.

6 thoughts on “Form validation using custom validator in Angular Application

  1. My brother recommended I might like this blog.
    He used to be totally right. This publish actually made my day.
    You can not consider simply how much time I had
    spent for this information! Thank you!

  2. If some one needs expert view regarding running a blog then i
    suggest him/her to pay a quick visit this web site,
    Keep up the pleasant work.

Leave a Reply

Your email address will not be published. Required fields are marked *