Wednesday, November 18, 2020

Custom Validator in Angular Reactive Form

Angular framework provides many built-in validators that can be used with forms but sometimes these built-in validators may not match the requirement of your use case. For such scenario Angular also gives the option to create a custom validator. In this tutorial we’ll see how to create a custom validator and how to use it with Angular reactive form.

Creating custom validator

Custom validators are also like any other function that you can write in a component class. A custom validator takes FormControl as an argument and returns a key, value pair where key is string and value is boolean. Following pseudo code shows how custom validator can be written to validate a control in Angular form.

myCustomValidator(control: FormControl): {[key: string]: boolean}{
  if(validation fails){
    return {invalid: true};
  }
  return null;
}

Whenever the value of the passed FormControl is changed Angular will automatically run this custom validator to check the validity of the control.

If validation fails then you return a key, value pair where key can be any string value and value is true. If validation is successful then you have to return nothing or null.

Angular Custom validator Reactive form example

In this custom validator example we’ll create an Angular reactive form to capture membership details and has a field ‘Membership Date’. We want to validate that the membership date is not less than the current date. To check that we’ll write a custom validator.

Initial form state

Angular reactive form validation

Data model (member.model.ts)

Member class defines the data model reflected in the form, we’ll bind values from the form to an object of this Member class.

export class Member {
  name: string;
  mail: string;
  membershipDate: Date;
  membershipType: string;
  constructor(name: string, mail: string, membershipDate: Date, membershipType: string) {
    this.name = name;
    this.mail = mail;
    this.membershipDate  = membershipDate;
    this.membershipType = membershipType;
  }
}

Component class (app.component.ts)

import { DatePipe } from '@angular/common';
import { Component, OnInit} from '@angular/core';
import { FormControl, FormGroup, Validators} from '@angular/forms';
import { Member } from './member.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html', 
  providers: [DatePipe]
})
export class AppComponent implements OnInit {
  membershiptypes = ['Silver', 'Gold', 'Platinum'];
  currentDate = new Date();
  member = new Member('', '', new Date(), '');
  submitted = false;
  membershipForm: FormGroup;
  constructor(private datePipe: DatePipe){ }
  ngOnInit() {
    this.membershipForm = new FormGroup({
      memberName: new FormControl(null, [Validators.required, Validators.minLength(5)]),
      email: new FormControl(null, [Validators.required, Validators.email]),  
      mdate: new FormControl(this.datePipe.transform(this.currentDate, 'yyyy-MM-dd'), this.dateValidation.bind(this)),
      membershipType: new FormControl('Silver')
    });
  }
  onSubmit(){
    this.submitted = true;
    this.member.name = this.membershipForm.value.memberName;
    this.member.mail = this.membershipForm.value.email;
    this.member.membershipDate = this.membershipForm.value.mdate;
    this.member.membershipType = this.membershipForm.value.membershipType; 
  }
  // custom validator
  dateValidation(control: FormControl): {[key: string]: boolean}{
    let curDate = new Date();
    // standardize date
    curDate = new Date(Date.UTC(curDate.getUTCFullYear(), curDate.getUTCMonth(), curDate.getUTCDate()));

    let inputDate = new Date(control.value);
    inputDate = new Date(Date.UTC(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), inputDate.getUTCDate()));

    if(curDate > inputDate){
      return {invalidDate: true};
    }
    return null;
  }
}

In the component class dateValidation() function is the custom validator written to check that the membership date should not be less than the current date. If validation fails then ‘invalidDate’ key is returned with the value as true.

Since it is a reactive form so form controls are defined in the component class. In the ngOnInit() method when the FormControl instance for ‘mdate’ is created, custom validator function is also added so that the Angular framework knows whenever value of this FormControl changes it has to run the bound custom validator.

Template (app.component.html)

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8">
      <h1>Membership Form</h1>
      <form [formGroup]="membershipForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
          <label for="memberName">Name</label>
          <input type="text" class="form-control" id="memberName"
            formControlName="memberName">       
          <div class="alert alert-danger" *ngIf="membershipForm.get('memberName').invalid 
            && membershipForm.get('memberName').touched">
            <div *ngIf="membershipForm.get('memberName').errors.required">
              Name is required.
            </div>
            <div *ngIf="membershipForm.get('memberName').errors.minlength">
              Name must be at least 5 characters long.
            </div>
          </div>                      
        </div>
        <div class="form-group">
          <label for="email">email</label>
          <input type="email" class="form-control" id="email"
          formControlName="email">  
          <div class="alert alert-danger" *ngIf="membershipForm.get('email').invalid 
              && membershipForm.get('email').touched">
            Please enter a valid email
          </div>                      
        </div>
        <div class="form-group">
          <label for="mdate">Membership Date</label>
          <input type="date" class="form-control" id="mdate"
          formControlName="mdate"> 
          <div class="alert alert-danger" *ngIf="membershipForm.get('mdate').hasError('invalidDate') 
          && membershipForm.get('mdate').touched">
            Membership date should  be equal to or greater than current date
          </div>                                  
        </div>
        <div class="form-group">
          <label for="type">Membership Type</label>
          <select class="form-control" id="type"                    
          formControlName="membershipType">
            <option *ngFor="let mtype of membershiptypes" [value]="mtype">{{mtype}}</option>
          </select>
        </div>
        <button type="submit" [disabled]="membershipForm.invalid" class="btn btn-success">Submit</button>
      </form> 
    </div>
  </div>
  <hr>
  <div *ngIf="submitted">
    <div class="row">
      <div class="col-xs-12 col-sm-10 col-md-8">
        <p>Name: {{member.name}}</p>
        <p>email: {{member.mail}}</p>
        <p>Membership Date: {{member.membershipDate | date:'dd/MM/yyyy'}}</p>
        <p>Membership Type: {{member.membershipType}}</p>
      </div>
    </div>
  </div>  
</div>

In the template you can check if mdate control has any error by passing the key which is returned from the custom validator.

formControlName="mdate"> 
          <div class="alert alert-danger" *ngIf=”membershipForm.get('mdate').hasError('invalidDate') 
          && membershipForm.get('mdate').touched">
            Membership date should  be equal to or greater than current date
          </div>                                  
        </div>

CSS class (src/assets/forms.css)

You will also need to add a CSS class to show the bars on the left side of the controls.

.ng-invalid.ng-untouched:not(form)  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid.ng-touched:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

In the index.html file, update the <head> tag to include the new style sheet.

<link rel="stylesheet" href="assets/forms.css">
Custom validator in Angular Reactive form

That's all for this topic Custom Validator in Angular Reactive Form. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Angular Reactive Form Validation Example
  2. Angular Template-Driven Form Validation Example
  3. Radio Button in Angular Form Example
  4. FormBuilder in Angular Example
  5. Angular Form setValue() and patchValue()

You may also like-

  1. Angular - Call One Service From Another
  2. Angular Route - Passing Static Data
  3. Angular Route Resolver - Passing Data Dynamically
  4. Angular Two-Way Data Binding With Examples
  5. Map Operation in Java Stream API
  6. StringBuilder Class in Java
  7. Print Odd-Even Numbers Using Threads And wait-notify Java Program
  8. Concatenating Lists in Python

No comments:

Post a Comment