LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 3516 min read

Form Validation

Go deeper into form validation with custom synchronous validators and async validators for server-side checks.

Built-in Validators Recap

Validators.required, Validators.email, Validators.minLength, and Validators.pattern cover a large share of everyday needs, applied as an array of functions when constructing a FormControl.

Writing a Custom Validator

A custom validator is a plain function matching Angular's ValidatorFn signature — it receives the control and returns either null (valid) or an error object.

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function noSpecialCharacters(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const hasSpecialChars = /[^a-zA-Z0-9]/.test(control.value ?? '');
return hasSpecialChars ? { specialCharacters: true } : null;
};
}
username: ['', [Validators.required, noSpecialCharacters()]],

Cross-Field Validation

A validator applied at the FormGroup level (rather than one field) can compare multiple controls — the standard pattern for something like a "confirm password" field.

function passwordsMatch(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
return password === confirm ? null : { passwordsMismatch: true };
};
}
signupForm = this.fb.group(
{
password: ['', Validators.required],
confirmPassword: ['', Validators.required],
},
{ validators: passwordsMatch() },
);

Async Validators

An async validator returns an Observable (or Promise) instead of a plain value — the standard tool for checks that require a server round-trip, like confirming a username is available.

import { map } from 'rxjs/operators';
function usernameAvailableValidator(usersService: UsersService): AsyncValidatorFn {
return (control: AbstractControl) => {
return usersService.checkUsernameAvailable(control.value).pipe(
map(isAvailable => (isAvailable ? null : { usernameTaken: true })),
);
};
}
Async Validators Run After Sync Ones Pass

Angular only runs async validators once all synchronous validators on a control have already passed, and marks the control as PENDING while the async check is in flight — useful for showing a "checking availability…" state.

Common Beginner Mistakes

Firing an async validator on every keystroke without debouncing

Combine it with updateOn: 'blur' on the control, or debounce the underlying request inside the validator, to avoid hammering your backend.

Forgetting server-side validation still matters

Just like any client-side form validation, Angular's validators are for UX — always re-validate on the server, since client-side checks can be bypassed.

FAQs

Yes — a validator is a plain, standalone function, so it's trivially reusable across any form in your app.

Check control.errors — it's an object keyed by each failing validator's name (e.g. errors?.['specialCharacters']), letting you show a tailored message per failure.

Summary

Custom, cross-field, and async validators extend Angular's form validation well beyond the built-in set, covering nearly any real-world validation need. With forms covered, the next section looks at organizing an app with NgModules and managing shared state.

Next Lesson →

NgModules (Legacy Module System)