Async Validators

Validate Against the Server (Debounced)

Async Validators

An async validator returns a Promise or Observable of ValidationErrors, letting you check things only the server knows — like email uniqueness.

4 min read Level 3/5 #angular#forms#async
What you'll learn
  • Write an AsyncValidatorFn
  • Apply it as the third FormControl argument
  • Combine with debounce for fewer requests

Some checks require a round trip — is this username available, does this discount code exist, is this email already registered. Async validators are how Angular models that.

The Signature

An async validator returns a Promise or Observable that emits ValidationErrors | null and then completes.

import { AsyncValidatorFn } from '@angular/forms';
import { inject } from '@angular/core';
import { map } from 'rxjs';
import { ApiService } from './api.service';

export function emailTaken(): AsyncValidatorFn {
  const api = inject(ApiService);
  return (c) =>
    api.checkEmail(c.value).pipe(
      map((taken) => (taken ? { taken: true } : null)),
    );
}

Wiring It Up

Async validators go in the third argument of FormControl, after the sync validators.

import { FormControl, Validators } from '@angular/forms';

const email = new FormControl(
  '',
  { validators: [Validators.required, Validators.email] },
);

// Or with the array form:
const email2 = new FormControl(
  '',
  [Validators.required, Validators.email],
  [emailTaken()],
);

While the request is in flight, control.status is 'PENDING'. Use that to show a spinner.

Debouncing

Angular runs async validators on every value change by default. That hammers your server. The fix is to opt out of the default and run a manual pipeline.

import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';

email.valueChanges
  .pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap((v) => api.checkEmail(v)),
  )
  .subscribe((taken) => {
    email.setErrors(taken ? { taken: true } : null);
  });

That manual approach gives you control over when the request fires. For most cases the built-in async validator is fine — just keep an eye on request volume.

FormArray — Dynamic Lists →