Using Async Validator in FormGroup in Angular 19
February 24, 2025
In this article, I will discuss how to create and use async validators in reactive form in our Angular application.
1. Angular provides
AsyncValidatorFn
function that handles async validation. It receives a control and returns Promise
or Observable
that emits validation error if present otherwise null.
import { AsyncValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'; export function existingUserNameValidator(service: CustomerService): AsyncValidatorFn { return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => { ------ }; }
FormControl
.
constructor(formState: any = null, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null)
3. Find the code to create
FormGroup
that is configuring async validator.
this.customerForm = new FormGroup({ username: new FormControl( '', [Validators.required], [existingUserNameValidator(this.customerService)] ), ------ });
FormGroup
. I am creating my example using Angular 19.
1. Create Async Validator Function using AsyncValidatorFn
username-exists-validator.tsimport { AsyncValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; import { CustomerService } from './customer.service'; export function existingUserNameValidator(service: CustomerService): AsyncValidatorFn { return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => { return service.userNameExists(control.value).pipe(map( users => { return users.length > 0 ? { "userNameExists": true } : null; } )); }; }
import { AsyncValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; import { CustomerService } from './customer.service'; export function existingMobNumValidator(service: CustomerService): AsyncValidatorFn { return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => { return service.mobNumExists(control.value).pipe(map( isAvailable => { return isAvailable ? { "mobNumExists": true } : null; } )); }; }
2. Create Service
customer.service.tsimport { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class CustomerService { userNameExists(username: string): Observable<string[]> { const users = ['aaaaa', 'bbbbb']; const arr = users.filter(u => u === username); return of(arr); } mobNumExists(mobileNumber: string): Observable<boolean> { const mobNums = ['1111111', '2222222'] const index = mobNums.findIndex(n => n === mobileNumber) return of(index < 0 ? false: true); } }
3. Create Component
customer.component.tsimport { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { FormGroup, Validators, ReactiveFormsModule, FormControl } from '@angular/forms'; import { existingMobNumValidator } from './existing-mobile-number-validator'; import { CustomerService } from './customer.service'; import { existingUserNameValidator } from './username-exists-validator'; @Component({ selector: 'my-app', imports: [ReactiveFormsModule, CommonModule], templateUrl: './customer.component.html', }) export class CustomerComponent implements OnInit { customerForm!: FormGroup; constructor(private customerService: CustomerService) { } ngOnInit() { this.customerForm = new FormGroup({ username: new FormControl( '', [Validators.required, Validators.maxLength(7)], [existingUserNameValidator(this.customerService)] ), mobNum: new FormControl('', [Validators.required], [existingMobNumValidator(this.customerService)]), }); } onFormSubmit() { console.log(this.customerForm.value); this.customerForm.reset(); } get username() { return this.customerForm.get('username') as FormControl; } get mobNum() { return this.customerForm.get('mobNum') as FormControl; } }
4. HTML Template
customer.component.html<form [formGroup]="customerForm" (ngSubmit)="onFormSubmit()"> <p>Username: <input formControlName="username"> @if(username.errors?.['required']) { <div style="color: red;">Username required. </div> } @if(username.errors?.['maxlength']) { <div style="color: red;">Max length is 7. </div> } @if(username.errors?.['userNameExists']) { <div style="color: red;">Username already exists. </div> } </p> <p>Mobile: <input formControlName="mobNum"> @if(mobNum.errors?.['required']) { <div style="color: red;">Mobile number required. </div> } @if(mobNum.errors?.['mobNumExists']) { <div style="color: red;">Mobile number already exists. </div> } </p> <p><button>Submit</button></p> </form>
5. Output
