Form Validation
Adding required fields, patterns, and custom rules to a form, and showing the right error message at the right time.
읽는 데 2분
A form that merely collects input isn't enough — most forms need to reject bad data and tell the user why. Angular's validation system works the same way across both reactive and template-driven forms: each control tracks its own validity and validation state.
Built-in validators
import { FormControl, Validators } from '@angular/forms';
const email = new FormControl('', [Validators.required, Validators.email]);
const age = new FormControl(0, [Validators.min(18), Validators.max(120)]);
const username = new FormControl('', [
Validators.required,
Validators.pattern(/^[a-zA-Z0-9_]{3,16}$/),
]);Each validator is a plain function that Angular runs against the control's current value, returning an error object when the value fails or null when it passes. Passing an array runs every validator in it, and the control is considered invalid if any of them fail.
Showing errors at the right moment
<input formControlName="email" />
@if (email.invalid && email.touched) {
@if (email.errors?.['required']) {
<p class="error">Email is required.</p>
}
@if (email.errors?.['email']) {
<p class="error">Enter a valid email address.</p>
}
}Checking email.touched alongside email.invalid matters as much as the validation rule itself — without it, every field would show an error message the instant the page loads, before the user has had a chance to type anything. touched becomes true once a control has been focused and then blurred, which is the conventional moment to start showing validation feedback. errors is an object keyed by whichever validators failed, letting you show a specific, relevant message rather than a generic "invalid" for every kind of mistake.
Writing a custom validator
Built-in validators cover common cases, but real rules often need something specific — confirming a password matches, checking a value against a list. A custom validator is just a function with the same signature as a built-in one:
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function passwordsMatchValidator(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
return password === confirm ? null : { passwordsMismatch: true };
};
}signupForm = new FormGroup(
{
password: new FormControl(''),
confirmPassword: new FormControl(''),
},
{ validators: passwordsMatchValidator() },
);This validator is attached to the FormGroup rather than a single control, because comparing two fields against each other requires access to both — a validator on confirmPassword alone would have no way to read password's current value.
Client-side validation is a UX layer, not a security boundary
Validating in the browser gives users immediate, helpful feedback, but it runs entirely on their machine and can be bypassed by anyone calling the API directly. Every rule enforced here for user experience — required fields, formats, ranges — needs to be enforced again on the server, which is the only validation that actually protects the data.