Angular Async Validator with Debounce

By Arvind Rai, September 20, 2023
On this page, we will learn to create async validator with debounce in our Angular application. In async validation, request can be sent over HTTP to validate data. To create an async validator, we need to create a Directive that implements AsyncValidator and override its validate() method. This method returns Promise or Observable instance.
Debounce is removing unwanted input noise. To use async validator with debounce, we can use RxJS debounceTime() method. Here on this page, I will create a demo application with async validators that is using debounceTime() method to debounce the unwanted request hits in validation.

RxJS debounceTime()

Find the RxJS debounceTime() time.
debounceTime(dueTime: number) 
The debounceTime emits a notification from the source Observable only after the specified time has passed without another source emission.
Find the code snippet.
fromEvent(document, 'click')
  .pipe(debounceTime(1000))
  .subscribe(data => console.log("You clicked :" + data)); 
If we click multiple times within 1 second, console.log() will print only one time.

Creating Async Validator with debounceTime()

To create async validator, create a Directive implementing AsyncValidator interface and define its validate() method.
@Directive({
  selector: '[mobNumExists]',
  standalone: true,
  providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: ExistingMobileNumValidatorDirective, multi: true }]
})
export class ExistingMobileNumValidatorDirective implements AsyncValidator {
  constructor(private userService: UserService) { }
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.userService.getMNumbers(control.value).pipe(
      debounceTime(1000),
      map(mobnum => {
          return (mobnum && mobnum.length > 0) ? { "mnumExists": true } : null;
        }
      ));
  }
} 
Use standalone: true for standalone application. Debounce time is 1000 milli second in the above code. In this duration for multiple request, the above custom validation will emit only one time.

Complete Example

existing-usrname-validator.ts
import { Directive } from '@angular/core';
import { AsyncValidator, NG_ASYNC_VALIDATORS, AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable } from "rxjs";
import { debounceTime, map } from "rxjs/operators";
import { UserService } from './user.service';

@Directive({
  selector: '[usernameExists]',
  standalone: true,
  providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: ExistingUsrnameValidatorDirective, multi: true }]
})
export class ExistingUsrnameValidatorDirective implements AsyncValidator {
  constructor(private userService: UserService) { }
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.userService.getUsrNames(control.value).pipe(
      debounceTime(1000),
      map((usernames: string[]) => {
        return (usernames && usernames.length > 0) ? { "unameExists": true } : null;
      })
    );
  }
} 
existing-mobno-validator.ts
import { Directive } from '@angular/core';
import { AsyncValidator, NG_ASYNC_VALIDATORS, AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable } from "rxjs";
import { debounceTime, map } from "rxjs/operators";
import { UserService } from './user.service';

@Directive({
  selector: '[mobNumExists]',
  standalone: true,
  providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: ExistingMobileNumValidatorDirective, multi: true }]
})
export class ExistingMobileNumValidatorDirective implements AsyncValidator {
  constructor(private userService: UserService) { }
  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.userService.getMNumbers(control.value).pipe(
      debounceTime(1000),
      map(mobnum => {
          return (mobnum && mobnum.length > 0) ? { "mnumExists": true } : null;
        }
      ));
  }
} 
user.component.ts
import { Component, OnInit } from '@angular/core';
import { NgForm, FormsModule } from '@angular/forms';
import { UserService } from './user.service';
import { CommonModule } from '@angular/common';
import { ExistingMobileNumValidatorDirective } from './existing-mobno-validator';
import { ExistingUsrnameValidatorDirective } from './existing-usrname-validator';

import { debounceTime, fromEvent } from 'rxjs';

@Component({
    selector: 'app-user',
    standalone: true,
    imports: [
        CommonModule,
        FormsModule,
        ExistingUsrnameValidatorDirective,
        ExistingMobileNumValidatorDirective
    ],
    templateUrl: './user.component.html' 
})
export class UserComponent implements OnInit {
    formSubmitted = false;
    constructor(
        private userService: UserService) {
    }
    ngOnInit(){
        fromEvent(document, 'click')
         .pipe(debounceTime(1000))
         .subscribe(data => console.log("You clicked :" + data));
    }
    onFormSubmit(usrForm: NgForm) {
        this.userService.saveUser(usrForm.value);
        this.formSubmitted = true;
        usrForm.reset();
    }
} 
user.component.html
<h3>Angular Async Validator with debounceTime</h3>
<div *ngIf="formSubmitted && usrForm.pristine" class="submitted"> Form submitted successfully. </div>
<form #usrForm="ngForm" (ngSubmit)="onFormSubmit(usrForm)">
  <div>User name:</div>
  <div>
    <input name="username" required usernameExists ngModel #uname="ngModel">
    <div *ngIf="uname?.errors?.['required']" class="error">
      Username required.
    </div>
    <div *ngIf="uname?.errors?.['unameExists']" class="error">
      User name already exists.
    </div>
  </div>
  <br />
  <div>Mobile Number:</div>
  <div>
    <input name="mobileNumber" required mobNumExists ngModel #mobnum="ngModel">
    <div *ngIf="mobnum?.errors?.['required']" class="error">
      Mobile number required.
    </div>
    <div *ngIf="mobnum?.errors?.['mnumExists']" class="error">
      Mobile number already registered.
    </div>
  </div>
  <div>
    <br /><button [disabled]="!usrForm.valid">Submit</button>
  </div>
</form> 
user.service.ts
import { Injectable } from '@angular/core';
import { of } from 'rxjs';

@Injectable()
export class UserService {
	saveUser(data: any) {
		console.log(data);
	}
	getUsrNames(uname: string) {
		if (uname === "Mohan") {
			return of(["Mohan"]);
		} else {
			return of([]);
		}
	}
	getMNumbers(mnum: string) {
		if (mnum === "222222") {
			return of(["222222"]);
		} else {
			return of([]);
		}
	}
}  
Find the print screen of the output.
Angular Async Validator Debounce Example

Reference

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us