What are custom validators in Angular?
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.
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.
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.
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
});