Skip to content
intermediate Phase 7 · Reactive Forms and Validation

Built-in and Custom Validators

Attach validation rules and write your own validator functions.

45m
0 problems
Topic Progress 0%

Built-in Validators

Angular provides a set of built-in validators that cover common validation scenarios.

Using Built-in Validators

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

this.form = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required, Validators.minLength(8)]],
  age: [null, [Validators.required, Validators.min(18), Validators.max(120)]],
  website: ['', [Validators.pattern(/^https?:\/\/.+/)]],
  username: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(20)]],
});

Built-in Validators Reference

Validator Purpose
Validators.required Field must not be empty
Validators.email Must be a valid email format
Validators.minLength(n) Minimum character length
Validators.maxLength(n) Maximum character length
Validators.min(n) Minimum numeric value
Validators.max(n) Maximum numeric value
Validators.pattern(regex) Must match regular expression
Validators.nullValidator Always valid (no-op)

Displaying Validation Errors

<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <label>
    Email
    <input type="email" formControlName="email">
  </label>

  <div *ngIf="form.get('email')?.invalid && form.get('email')?.touched">
    <p *ngIf="form.get('email')?.errors?.['required']">Email is required</p>
    <p *ngIf="form.get('email')?.errors?.['email']">Invalid email format</p>
  </div>

  <label>
    Password
    <input type="password" formControlName="password">
  </label>

  <div *ngIf="form.get('password')?.invalid && form.get('password')?.touched">
    <p *ngIf="form.get('password')?.errors?.['required']">Password is required</p>
    <p *ngIf="form.get('password')?.errors?.['minlength']">
      Password must be at least 8 characters
    </p>
  </div>

  <button type="submit" [disabled]="form.invalid">Submit</button>
</form>

Validation State Properties

const control = this.form.get('email');

control?.valid;      // true if all validators pass
control?.invalid;    // true if any validator fails
control?.dirty;      // true if the user has changed the value
control?.pristine;   // true if the user has NOT changed the value
control?.touched;    // true if the user has focused and blurred the field
control?.untouched;  // true if the user has NOT focused the field
control?.errors;     // { required: true, email: true, ... } or null
control?.pending;    // true while async validators are running

Custom and Async Validators

Custom validators are functions that return null for valid input or an error object for invalid input.

Custom Validator Function

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function passwordStrengthValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = control.value;
    if (!value) return null;

    const hasUpperCase = /[A-Z]/.test(value);
    const hasLowerCase = /[a-z]/.test(value);
    const hasNumber = /[0-9]/.test(value);
    const hasSpecial = /[!@#$%^&*]/.test(value);

    const valid = hasUpperCase && hasLowerCase && hasNumber && hasSpecial;

    return valid ? null : {
      passwordStrength: {
        hasUpperCase,
        hasLowerCase,
        hasNumber,
        hasSpecial,
      },
    };
  };
}
this.form = this.fb.group({
  password: ['', [Validators.required, passwordStrengthValidator()]],
});

Cross-Field Validation

export function passwordMatchValidator: ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const password = control.get('password')?.value;
    const confirmPassword = control.get('confirmPassword')?.value;

    return password === confirmPassword ? null : { passwordMismatch: true };
  };
}

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

Async Validators

Async validators run after built-in checks and are useful for server-side validation:

export function uniqueUsernameValidator(userService: UserService): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    if (!control.value) return of(null);

    return userService.checkUsername(control.value).pipe(
      map(isTaken => (isTaken ? { usernameTaken: true } : null)),
      catchError(() => of(null))
    );
  };
}
this.form = this.fb.group({
  username: ['', {
    validators: [Validators.required],
    asyncValidators: [uniqueUsernameValidator(this.userService)],
    updateOn: 'blur',
  }],
});

updateOn Option

Controls when validation and value updates occur:

// Default — on every value change
this.fb.control('', { updateOn: 'change' })

// On blur — when the field loses focus
this.fb.control('', { updateOn: 'blur' })

// On submit — only when the form is submitted
this.fb.control('', { updateOn: 'submit' })

Quiz

1. What does a custom validator return for valid input?

Question 1 options

2. How do you implement cross-field validation?

Question 2 options

3. What is the purpose of async validators?

Question 3 options

Flashcards

Question

What do custom validators return?

Answer

null for valid input, ValidationErrors object for invalid input.

Question

How do you do cross-field validation?

Answer

Add a validator to the FormGroup that receives the entire group and checks multiple controls.

Question

What are async validators for?

Answer

Server-side validation checks. Return Observable<ValidationErrors | null>.

Revision Notes

Key Takeaways

  • 1. Built-in validators cover common scenarios
  • 2. Custom validators return null or ValidationErrors
  • 3. Cross-field validation goes on the FormGroup
  • 4. Async validators handle server-side checks

Interview Tips

  • • Explain the ValidatorFn signature
  • • Be ready to describe cross-field validation
  • • Mention updateOn for controlling validation timing

Cheat Sheet

Form Validation Cheat Sheet

Built-in validators:

Validators.required
Validators.email
Validators.minLength(8)
Validators.pattern(/regex/)

Custom validator:

export function myValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    return control.value ? null : { myError: true };
  };
}

Cross-field:

this.fb.group({...}, { validators: crossFieldValidator });

Async:

{ validators: [...], asyncValidators: [asyncFn] }

Continue Learning