Validation
| Since: | Angular 14(2022) |
|---|
Validation in Angular is a mechanism for defining rules to verify form input values. It is available in both template-driven forms and reactive forms, and supports built-in validators such as required input, minimum/maximum length, and regular expression patterns, as well as custom validators you create yourself. Validation results can be referenced from the template through the errors object, and used for displaying error messages and controlling the submit button.
Built-in Validator List
| Validator | Template-Driven | Reactive (Validators) | Description |
|---|---|---|---|
| Required | required | Validators.required | Validates that the value is not empty. |
| Minimum length | minlength="N" | Validators.minLength(N) | Validates that the string length is N characters or more. |
| Maximum length | maxlength="N" | Validators.maxLength(N) | Validates that the string length is N characters or fewer. |
| Pattern | pattern="regex" | Validators.pattern('regex') | Validates that the input value matches the specified regular expression. |
email | Validators.email | Validates that the input value is in email address format. | |
| Minimum value | min="N" | Validators.min(N) | Validates that the number is N or greater. |
| Maximum value | max="N" | Validators.max(N) | Validates that the number is N or less. |
Sample Code
An example of using built-in validators in a template-driven form.
// register.component.ts
// A component that validates a user registration form with template-driven forms.
// Import FormsModule to use ngModel and built-in validators.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-register',
standalone: true,
imports: [FormsModule],
template: `
<form #registerForm="ngForm" (ngSubmit)="onSubmit(registerForm)">
<div>
<label for="name">Name</label>
<input
id="name"
name="name"
[(ngModel)]="name"
required
minlength="2"
maxlength="20"
#nameCtrl="ngModel"
/>
<div *ngIf="nameCtrl.invalid && nameCtrl.touched">
<span *ngIf="nameCtrl.errors?.['required']">Name is required.</span>
<span *ngIf="nameCtrl.errors?.['minlength']">Please enter at least 2 characters.</span>
</div>
</div>
<div>
<label for="email">Email address</label>
<input
id="email"
name="email"
type="email"
[(ngModel)]="email"
required
email
#emailCtrl="ngModel"
/>
<div *ngIf="emailCtrl.invalid && emailCtrl.touched">
<span *ngIf="emailCtrl.errors?.['required']">Email address is required.</span>
<span *ngIf="emailCtrl.errors?.['email']">Please enter a valid email address.</span>
</div>
</div>
<button type="submit" [disabled]="registerForm.invalid">Register</button>
</form>
`,
})
export class RegisterComponent {
name: string = '';
email: string = '';
onSubmit(form: any): void {
if (form.valid) {
console.log('Submitted:', { name: this.name, email: this.email });
}
}
}
An example of using Validators in a reactive form.
// login.component.ts
// A component that validates a login form with reactive forms.
// Combine FormBuilder and Validators to define validation rules.
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-login',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div>
<label for="email">Email address</label>
<input id="email" type="email" formControlName="email" />
<div *ngIf="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
<span *ngIf="loginForm.get('email')?.errors?.['required']">Email address is required.</span>
<span *ngIf="loginForm.get('email')?.errors?.['email']">Please enter a valid format.</span>
</div>
</div>
<div>
<label for="password">Password</label>
<input id="password" type="password" formControlName="password" />
<div *ngIf="loginForm.get('password')?.invalid && loginForm.get('password')?.touched">
<span *ngIf="loginForm.get('password')?.errors?.['required']">Password is required.</span>
<span *ngIf="loginForm.get('password')?.errors?.['minlength']">Please enter at least 8 characters.</span>
</div>
</div>
<button type="submit" [disabled]="loginForm.invalid">Log in</button>
</form>
`,
})
export class LoginComponent {
loginForm: FormGroup = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
});
constructor(private fb: FormBuilder) {}
onSubmit(): void {
if (this.loginForm.valid) {
console.log('Login info:', this.loginForm.value);
}
}
}
An example of creating a custom validator and applying it to a reactive form.
// custom-validators.ts
// A file that defines custom validation functions for the app.
// Returning a function of type ValidatorFn conforms to Angular's validator specification.
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// A validator that checks whether two password fields match.
// Applied at the FormGroup level.
export function passwordMatchValidator(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
// Skip validation if either field is empty
if (!password || !confirm) {
return null;
}
return password === confirm ? null : { passwordMismatch: true };
};
}
// A validator that allows only alphabetic characters and hyphens (for usernames).
export function usernameValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value: string = control.value;
// Return null if empty — let required handle it
if (!value) {
return null;
}
const isValid = /^[a-zA-Z0-9_-]+$/.test(value);
return isValid ? null : { invalidUsername: true };
};
}
// signup.component.ts
// A member registration form component with custom validators applied.
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { usernameValidator, passwordMatchValidator } from './custom-validators';
@Component({
selector: 'app-signup',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<div>
<label for="username">Username</label>
<input id="username" formControlName="username" />
<div *ngIf="signupForm.get('username')?.invalid && signupForm.get('username')?.touched">
<span *ngIf="signupForm.get('username')?.errors?.['required']">Username is required.</span>
<span *ngIf="signupForm.get('username')?.errors?.['invalidUsername']">Only letters, numbers, hyphens, and underscores are allowed.</span>
</div>
</div>
<div>
<label for="pw">Password</label>
<input id="pw" type="password" formControlName="password" />
</div>
<div>
<label for="cpw">Confirm Password</label>
<input id="cpw" type="password" formControlName="confirmPassword" />
<div *ngIf="signupForm.errors?.['passwordMismatch'] && signupForm.get('confirmPassword')?.touched">
Passwords do not match.
</div>
</div>
<button type="submit" [disabled]="signupForm.invalid">Register</button>
</form>
`,
})
export class SignupComponent {
signupForm: FormGroup;
constructor(private fb: FormBuilder) {
this.signupForm = this.fb.group(
{
username: ['', [Validators.required, usernameValidator()]],
password: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: ['', Validators.required],
},
{ validators: passwordMatchValidator() }
);
}
onSubmit(): void {
if (this.signupForm.valid) {
console.log('Registration data:', this.signupForm.value);
}
}
}
Control State Flag List
| Property | Description |
|---|---|
valid / invalid | Indicates whether all validations have passed or not. |
touched / untouched | Indicates whether the field has lost focus at least once. Used to show errors at the right time. |
dirty / pristine | Indicates whether the value has been changed at least once. |
pending | Indicates that an async validator has not yet completed. |
errors | An object holding validation error keys and details. Returns null when there are no errors. |
Important Notes
| Item | Description |
|---|---|
| Template-driven vs Reactive | Template-driven forms are suited for smaller forms, while reactive forms are better for complex validation logic and dynamic form structures. |
Using touched | Showing errors immediately when the page loads degrades the user experience. It is common practice to add touched as a condition so errors only appear after the user has left the field. |
| Custom validator return value | Always return null when validation succeeds. Returning an error object marks validation as failed. |
| Cross-field validation | Validation that spans multiple fields (such as password matching) should be applied at the FormGroup level. |
| Async validators | Use AsyncValidatorFn for validations that require a server query (such as checking for duplicate usernames). Implement as a function that returns an Observable or Promise. |
Overview
Validation in Angular is a flexible mechanism available in both template-driven forms and reactive forms. Built-in validators such as required and minlength cover most cases, and custom validators can be implemented for more complex verifications.
For error display timing, use touched and dirty to prevent errors from appearing before the user has interacted. When validation spanning multiple fields is needed, applying a validator at the FormGroup level is the correct approach.
For the basics of form usage, see ngModel. For managing form data with services, see Service.
If you find any errors or copyright issues, please contact us.