Validation is a critical part of any application to ensure data integrity and provide meaningful feedback to users. Angular provides several built-in validators like required
, minLength
, maxLength
, email
, and pattern
. However, many real-world scenarios require custom validation logic that goes beyond the built-in validators.
In this article, we will explore custom validators in Angular, covering their creation, usage in reactive and template-driven forms, advanced techniques, and best practices.
Table of Contents
- Introduction to Custom Validators
- Why Use Custom Validators
- Built-in Validators vs Custom Validators
- Creating a Simple Custom Validator
- Using Custom Validators in Reactive Forms
- Using Custom Validators in Template-Driven Forms
- Parameterized Custom Validators
- Asynchronous Custom Validators
- Nested Form Validation with Custom Validators
- FormGroup and Cross-Field Validation
- Error Display for Custom Validators
- Combining Multiple Validators
- Real-World Example: Username Validation
- Real-World Example: Password Strength Validator
- Common Mistakes and Pitfalls
- Best Practices for Custom Validators
- Conclusion
1. Introduction to Custom Validators
A custom validator in Angular is a function that checks the value of a form control and returns:
null
if the control is valid- An error object if the control is invalid
Custom validators allow developers to implement complex validation logic that built-in validators cannot handle.
2. Why Use Custom Validators
While Angular’s built-in validators handle common scenarios, there are many cases that require custom rules:
- Restricting usernames to alphanumeric characters only
- Checking that passwords contain uppercase, lowercase, numbers, and symbols
- Validating that two fields (like password and confirm password) match
- Ensuring a date is within a specific range
Custom validators give developers the flexibility to implement business-specific rules for their forms.
3. Built-in Validators vs Custom Validators
Built-in Validators | Custom Validators |
---|---|
required | Checks if a control is empty |
minLength / maxLength | Checks string length |
pattern | Matches regex |
email | Checks email format |
Custom validators complement built-in validators and are often used together.
4. Creating a Simple Custom Validator
A custom validator is a function that takes a FormControl
as input and returns an error object or null:
import { AbstractControl, ValidationErrors, ValidatorFn, FormControl } from '@angular/forms';
export function noSpecialChars(control: FormControl): ValidationErrors | null {
const regex = /^[a-zA-Z0-9]+$/;
return regex.test(control.value) ? null : { invalidChars: true };
}
regex.test(control.value)
checks if the value contains only alphanumeric characters.{ invalidChars: true }
is returned if validation fails.null
is returned if validation passes.
5. Using Custom Validators in Reactive Forms
Custom validators can be added to FormControl
or FormGroup
:
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { noSpecialChars } from './validators';
@Component({
selector: 'app-user-form',
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="username" placeholder="Username" />
<div *ngIf="userForm.get('username')?.errors?.invalidChars">
Username contains invalid characters.
</div>
<button type="submit">Submit</button>
</form>
`
})
export class UserFormComponent {
userForm = new FormGroup({
username: new FormControl('', [Validators.required, noSpecialChars])
});
onSubmit() {
if (this.userForm.valid) {
console.log('Form Submitted', this.userForm.value);
}
}
}
- Combine custom validators with built-in validators like
Validators.required
. - Error messages can be displayed based on the custom validation result.
6. Using Custom Validators in Template-Driven Forms
Template-driven forms can also use custom validators via NG_VALIDATORS
:
import { Directive } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
@Directive({
selector: '[appNoSpecialChars]',
providers: [{ provide: NG_VALIDATORS, useExisting: NoSpecialCharsDirective, multi: true }]
})
export class NoSpecialCharsDirective implements Validator {
validate(control: AbstractControl): ValidationErrors | null {
const regex = /^[a-zA-Z0-9]+$/;
return regex.test(control.value) ? null : { invalidChars: true };
}
}
Usage in template:
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input name="username" ngModel appNoSpecialChars required />
<div *ngIf="userForm.controls.username?.errors?.invalidChars">
Username contains invalid characters.
</div>
<button type="submit">Submit</button>
</form>
- Directives allow template-driven forms to use the same validation logic as reactive forms.
7. Parameterized Custom Validators
Validators can accept parameters for dynamic behavior:
export function minLengthCustom(min: number) {
return (control: FormControl): ValidationErrors | null => {
return control.value.length >= min ? null : { minLengthCustom: { requiredLength: min } };
};
}
// Usage
username: new FormControl('', [minLengthCustom(5)])
- Allows reusing the validator for different fields with different rules.
- Error object can include parameter details for dynamic error messages.
8. Asynchronous Custom Validators
Some validations require server-side checks:
import { AbstractControl } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { delay, map } from 'rxjs/operators';
export function asyncUsernameValidator(control: AbstractControl): Observable<ValidationErrors | null> {
const forbiddenUsernames = ['admin', 'superuser'];
return of(forbiddenUsernames.includes(control.value)).pipe(
delay(1000),
map(isForbidden => (isForbidden ? { forbiddenName: true } : null))
);
}
Usage:
username: new FormControl('', Validators.required, asyncUsernameValidator)
- Asynchronous validators return an observable or promise.
- Useful for server-side validation like checking existing usernames or emails.
9. Nested Form Validation with Custom Validators
Custom validators can validate FormGroups:
import { FormGroup } from '@angular/forms';
export function passwordsMatch(group: FormGroup): ValidationErrors | null {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
return password === confirm ? null : { passwordsMismatch: true };
}
// Usage
new FormGroup({
password: new FormControl('', Validators.required),
confirmPassword: new FormControl('', Validators.required)
}, { validators: passwordsMatch });
- Ensures two fields match.
- Works at the group level rather than individual controls.
10. FormGroup and Cross-Field Validation
Cross-field validation is essential for:
- Password confirmation
- Start date and end date validation
- Dependent fields
Example: Validating date range:
export function dateRangeValidator(group: FormGroup): ValidationErrors | null {
const start = group.get('startDate')?.value;
const end = group.get('endDate')?.value;
return start <= end ? null : { invalidDateRange: true };
}
11. Error Display for Custom Validators
Show error messages dynamically:
<div *ngIf="userForm.get('username')?.errors?.invalidChars">
Username contains invalid characters.
</div>
<div *ngIf="userForm.errors?.passwordsMismatch">
Passwords do not match.
</div>
- Use
errors
property ofFormControl
orFormGroup
. - Combine with Angular structural directives like
*ngIf
.
12. Combining Multiple Validators
You can combine multiple built-in and custom validators:
new FormControl('', [
Validators.required,
Validators.minLength(6),
noSpecialChars
])
- Validators are evaluated in order.
- If one fails, others are still executed to accumulate errors.
13. Real-World Example: Username Validation
this.userForm = new FormGroup({
username: new FormControl('', [Validators.required, noSpecialChars])
});
- Prevents users from entering invalid characters.
- Ensures usernames are alphanumeric.
- Provides immediate feedback on invalid input.
14. Real-World Example: Password Strength Validator
export function strongPassword(control: FormControl): ValidationErrors | null {
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
return regex.test(control.value) ? null : { weakPassword: true };
}
this.userForm = new FormGroup({
password: new FormControl('', [Validators.required, strongPassword])
});
- Enforces uppercase, lowercase, number, and minimum length.
- Strengthens security for user accounts.
15. Common Mistakes and Pitfalls
- Returning
false
instead ofnull
for valid input. - Forgetting to include validators in the form control array.
- Not handling asynchronous validators properly.
- Overcomplicating simple validation that could be handled with built-ins.
- Not displaying error messages correctly in the template.
16. Best Practices for Custom Validators
- Always return
null
for valid input. - Include clear error object keys for template use.
- Use parameterized validators for reusability.
- Combine built-in and custom validators logically.
- Use asynchronous validators for server-side checks.
- Keep validators pure and side-effect-free.
- Test custom validators thoroughly.
Leave a Reply