Custom Form Validation in Angular

Hanish Totlani

In this post I’ll be sharing how to build a custom from validator for Angular apps and add this validator to Template-Driven Forms or Reactive Forms.

Here, I’m going to demonstrate the validation of a phone number input field, which should be of 10 digits.

Here are two screenshots that illustrate what our validation will look-like UI-wise:

Screenshot: Invalid Phone number

Screenshot: Valid Phone number

Validator for Template-Driven Form

For validation in template-driven forms, directives are used, so let’s go ahead and create a phone-number-validator directive.

phone-number-validator.ts</span>

import { Directive } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';

@Directive({
  selector: '[phoneValidateDirective]',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: AppPhoneValidateDirective,
    multi: true
  }]
})
export class AppPhoneValidateDirective implements Validator {
  validate(control: AbstractControl) : {[key: string]: any} | null {
    if (control.value && control.value.length != 10) {
      return { 'phoneNumberInvalid': true }; // return object if the validation is not passed.
    }
    return null; // return null if validation is passed.
  }
}

Don’t forget to register and add the validator to the existing validator array NG_VALIDATORS provided by Angular:

// ...
providers: [{
  provide: NG_VALIDATORS,
  useExisting: Your_Class_Name,
  multi: true
}]
// ...

Here I’ve created the phone directive which implements Validator of @angular/forms, for which we have to provide the following implementation method: validate(control: AbstractControl): : {[key: string]: any} | null. This validator will return an object if the validation is not passed which is { 'phoneNumberInvalid': true } and will return null if the validation is passed.

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 and if the errors property is empty then the form is valid.

app.component.html</span>

<div class="form-group col-sm-4">
  <label for="">Phone</label>
  <input type="text" class="form-control" name="phone" [(ngModel)]="phone" [class.is-invalid]="phonengModel.errors?.phoneNumberInvalid && (phonengModel.touched || phonengModel.dirty)" #phonengModel="ngModel" phoneValidateDirective>    <!-- Added directive to validate phone -->
  <span class="invalid-feedback" *ngIf="(phonengModel.touched || phonengModel.dirty) && phonengModel.errors?.phoneNumberInvalid"> <!-- Checked the errors property contains the 'phoneNumberInvalid' property or not which is returned by the validation function -->
      Phone number must be of 10 digit
  </span>
</div>

Validator for Reactive Forms

For validation in Reactive Forms we have to create a function.

app.component.ts</span>

import { FormBuilder, AbstractControl } from '@angular/forms';
import { Component, OnInit } from "@angular/core";

@Component({
  selector: 'reactive-form',
  templateUrl: './reactive-form.component.html'
})
export class AppReactiveForm implements OnInit {
  myForm: FormGroup;

  constructor(
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      phone: ['', [ValidatePhone]] // added the function in validators array of form-control
    });
  }
}

function ValidatePhone(control: AbstractControl): {[key: string]: any} | null  {
  if (control.value && control.value.length != 10) {
    return { 'phoneNumberInvalid': true };
  }
  return null;
}

We are adding the function to the validators array of FormControl.

app.component.html</span>

<form [formGroup]="myForm" novalidate class="needs-validation" (ngSubmit)="saveForm(myForm)">
  <div class="row">
    <div class="form-group col-sm-4">
      <label for="">Password</label>
      <input type="text" class="form-control " formControlName="phone" [class.is-invalid]="(myForm.get('phone').touched || myForm.get('phone').dirty) && myForm.get('password').">
      <span class="invalid-feedback" *ngIf="(myForm.get('password').touched || myForm.get('password').dirty) && myForm.get('password').invalid">
        Password is required
      </span> 
    </div>
  </div> 
</form>

Combining Both Validators

We can combine both validators so that we do not repeat our code and follow DRY (Don’t Repeat Yourself) principles:

phone-number-validator.ts</span>

import { Directive } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';

export function ValidatePhone(control: AbstractControl): {[key: string]: any} | null  {
    if (control.value && control.value.length != 10) {
        return { 'phoneNumberInvalid': true };
    }
    return null;
}

@Directive({
    selector: '[phone]',
    providers: [{
        provide: NG_VALIDATORS,
        useExisting: AppPhoneValidateDirective,
        multi: true
    }]
})
export class AppPhoneValidateDirective implements Validator {
    validate(control: AbstractControl) : {[key: string]: any} | null {
    return ValidatePhone(control);
    }
}

That’s it for now! For a deeper understanding of the concepts from this post, visit this post on Providers and read about AbstractControl

  Tweet It

🕵 Search Results

🔎 Searching...