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?
2. How do you implement cross-field validation?
3. What is the purpose of async validators?
Flashcards
Question
What do custom validators return?
Click to reveal answer
Answer
null for valid input, ValidationErrors object for invalid input.
Question
How do you do cross-field validation?
Click to reveal answer
Answer
Add a validator to the FormGroup that receives the entire group and checks multiple controls.
Question
What are async validators for?
Click to reveal answer
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] }