🅰️ Angular Q47 / 125

What are custom validators in Angular?

AI-Powered Answer ✓ Answered

In Angular, custom validators are functions that allow developers to define specific validation logic beyond the set of built-in validators (such as `required`, `minLength`, `pattern`). They are crucial for implementing unique business rules or complex validation scenarios that are not covered by Angular's standard validation API, ensuring data integrity and a better user experience.

What are Custom Validators?

A custom validator in Angular is a function that receives an AbstractControl instance (representing a FormControl, FormGroup, or FormArray) as an argument. It returns either a ValidationErrors object (if validation fails) or null (if validation passes). For asynchronous validation, it returns a Promise<ValidationErrors | null> or an Observable<ValidationErrors | null>.

Why Use Custom Validators?

  • Implementing unique business rules (e.g., password must contain a special character).
  • Cross-field validation (e.g., confirm password must match password).
  • Asynchronous validation (e.g., checking if a username is already taken on the server).
  • Reusing complex validation logic across multiple forms or controls.

Implementing a Custom Validator

Custom validators are typically implemented as functions that return a ValidatorFn or AsyncValidatorFn. These functions can take configuration arguments if the validator needs to be parameterized.

Synchronous Validator Example

This example shows a validator that checks if a control's value contains at least one digit.

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

export function containsDigitValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = control.value;
    if (!value) {
      return null; // Don't validate empty values, use 'required' instead
    }
    const hasDigit = /[0-9]/.test(value);
    return hasDigit ? null : { containsDigit: { value: control.value } };
  };
}

Asynchronous Validator Example

This example simulates an asynchronous validator that checks if a username is available after a delay, typically used for server-side validation.

typescript
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, delay } from 'rxjs/operators';

// Simulate a service call
function checkUsernameAvailability(username: string): Observable<boolean> {
  const existingUsernames = ['admin', 'guest', 'user123'];
  const isAvailable = !existingUsernames.includes(username);
  return of(isAvailable).pipe(delay(500)); // Simulate network delay
}

export function uniqueUsernameValidator(): AsyncValidatorFn {
  return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    const username = control.value;
    if (!username) {
      return of(null); // Don't validate empty values
    }
    return checkUsernameAvailability(username).pipe(
      map(isAvailable => (isAvailable ? null : { uniqueUsername: true })),
      catchError(() => of({ uniqueUsername: true })) // Handle errors, e.g., network down
    );
  };
}

Applying Custom Validators

Custom validators are applied to FormControl instances in the same way as built-in validators, either during instantiation or using setValidators() and setAsyncValidators() methods.

typescript
import { FormControl, Validators } from '@angular/forms';
import { containsDigitValidator, uniqueUsernameValidator } from './custom-validators';

const usernameControl = new FormControl('', {
  validators: [Validators.required, Validators.minLength(4), containsDigitValidator()],
  asyncValidators: [uniqueUsernameValidator()],
  updateOn: 'blur' // Optional: validate on blur for async validators
});