Form Validation
Built-in Validators
1import { Validators } from '@angular/forms';
2
3this.fb.group({
4 // Required field
5 name: ['', Validators.required],
6
7 // Email
8 email: ['', [Validators.required, Validators.email]],
9
10 // Length
11 password: ['', [Validators.required, Validators.minLength(8), Validators.maxLength(50)]],
12
13 // Numeric range
14 age: [0, [Validators.min(18), Validators.max(120)]],
15
16 // Regex pattern
17 phone: ['', Validators.pattern(/^\d{9}$/)],
18
19 // Required true (checkbox)
20 acceptTerms: [false, Validators.requiredTrue]
21});
Custom Validators
1import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
2
3// Synchronous validator
4function forbiddenNameValidator(forbiddenNames: string[]): ValidatorFn {
5 return (control: AbstractControl): ValidationErrors | null => {
6 if (forbiddenNames.includes(control.value?.toLowerCase())) {
7 return { forbiddenName: { value: control.value } };
8 }
9 return null;
10 };
11}
12
13// Password strength validator
14function strongPasswordValidator(): ValidatorFn {
15 return (control: AbstractControl): ValidationErrors | null => {
16 const value = control.value;
17 if (!value) return null;
18
19 const hasUpperCase = /[A-Z]/.test(value);
20 const hasLowerCase = /[a-z]/.test(value);
21 const hasNumeric = /[0-9]/.test(value);
22 const hasSpecial = /[!@#$%^&*]/.test(value);
23
24 const isValid = hasUpperCase && hasLowerCase && hasNumeric && hasSpecial;
25
26 return isValid ? null : {
27 weakPassword: {
28 hasUpperCase,
29 hasLowerCase,
30 hasNumeric,
31 hasSpecial
32 }
33 };
34 };
35}
36
37// Usage
38this.fb.group({
39 name: ['', [Validators.required, forbiddenNameValidator(['admin', 'root'])]],
40 password: ['', [Validators.required, strongPasswordValidator()]]
41});
Async Validator
1import { AsyncValidatorFn } from '@angular/forms';
2import { map, catchError, debounceTime, switchMap, first } from 'rxjs/operators';
3import { of } from 'rxjs';
4
5function uniqueNameValidator(samuraiService: SamuraiService): AsyncValidatorFn {
6 return (control: AbstractControl) => {
7 return of(control.value).pipe(
8 debounceTime(300),
9 switchMap(name =>
10 samuraiService.checkNameExists(name).pipe(
11 map(exists => exists ? { nameTaken: true } : null),
12 catchError(() => of(null))
13 )
14 ),
15 first()
16 );
17 };
18}
19
20// Usage - third argument is async validators
21this.fb.group({
22 name: ['',
23 [Validators.required],
24 [uniqueNameValidator(this.samuraiService)]
25 ]
26});