Skip to content

Angular Form Validation

Validation ensures users provide correct, complete data before a form submits. This lesson covers Angular's built-in validators, combining them, and displaying errors clearly.

How Angular Validation Works

Every FormControl tracks its own validity by running one or more validator functions against its current value, each returning either null (valid) or an error object describing what failed. Angular ships common validators (required, email, minLength, and more) in the Validators class.

A FormGroup's overall validity is derived from its child controls, if any control is invalid, the whole group is invalid too.

form = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  age: [null, [Validators.required, Validators.min(18)]],
});

Multiple validators can be combined in an array; the control is invalid if any one of them fails.

Applying Validators

Validators.required
Validators.email
Validators.minLength(3)
Validators.maxLength(50)
Validators.min(0)
Validators.max(100)
Validators.pattern(/^[A-Za-z]+$/)
Validators.compose([v1, v2])
  • Pass a single validator function or an array of validators when defining a control.
  • Validators.pattern accepts a regular expression (or string) to validate against.
  • Validators.compose combines multiple validators into a single function, mainly used internally.
  • Custom validator functions follow the exact same signature as built-in ones (covered in the next lesson).

Built-in Validators Cheatsheet

The validators shipped in the Validators class.

Validator Checks
Validators.required Value is not empty/null/undefined
Validators.requiredTrue Value is exactly true (checkboxes)
Validators.email Value matches an email address pattern
Validators.minLength(n) String/array length is at least n
Validators.maxLength(n) String/array length is at most n
Validators.min(n) Numeric value is at least n
Validators.max(n) Numeric value is at most n
Validators.pattern(re) Value matches the given regular expression

Displaying Validation Errors

The errors object on an invalid control tells you exactly which validators failed, letting you show specific, helpful messages instead of a generic "invalid" notice.

<input formControlName="email" />
@if (form.get('email')?.invalid && form.get('email')?.touched) {
  @if (form.get('email')?.hasError('required')) {
    <p>Email is required.</p>
  }
  @if (form.get('email')?.hasError('email')) {
    <p>Please enter a valid email address.</p>
  }
}

Asynchronous Validators

Some validation requires a server round-trip, checking whether a username is already taken, for example. Async validators return an Observable or Promise instead of a synchronous result, and Angular marks the control PENDING while waiting.

export function uniqueUsernameValidator(userService: UserService): AsyncValidatorFn {
  return (control: AbstractControl) =>
    userService.checkUsername(control.value).pipe(
      map(isTaken => (isTaken ? { usernameTaken: true } : null))
    );
}

username = new FormControl('', {
  asyncValidators: [uniqueUsernameValidator(this.userService)],
});

Cross-Field Validation

Some validation rules depend on more than one field, confirming a password matches, or that an end date is after a start date. These are attached at the FormGroup level rather than on a single control.

function passwordsMatchValidator(group: AbstractControl): ValidationErrors | null {
  const password = group.get('password')?.value;
  const confirm = group.get('confirmPassword')?.value;
  return password === confirm ? null : { passwordsMismatch: true };
}

form = this.fb.group(
  { password: [''], confirmPassword: [''] },
  { validators: passwordsMatchValidator }
);

Common Mistakes

  • Showing raw validator error keys directly to users instead of translating them into friendly messages.
  • Attaching a cross-field validator to an individual control instead of the parent FormGroup.
  • Not accounting for the PENDING status while an async validator is running, showing a stale valid/invalid state.
  • Forgetting that Validators.required treats 0 and false as valid (non-empty) values, unlike an empty string.

Key Takeaways

  • Angular's Validators class ships common synchronous validators like required, email, and minLength.
  • A control's errors object identifies exactly which validators are currently failing.
  • Async validators support server-side checks and mark the control PENDING while resolving.
  • Cross-field validation is attached at the FormGroup level, not on individual controls.

Pro Tip

Debounce async validators ({ updateOn: 'blur' } or combining with debounceTime on valueChanges) to avoid firing a server request on every keystroke while a user is still typing.