Tutorial

Custom Form Validation in Angular

AngularJS

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

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
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
<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
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
<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
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

0 Comments

Creative Commons License