Validators and Error Display
Reactive forms become powerful when combined with validation. Angular ships with built-in validators for the most common rules, and you can write custom validators for anything else.
Adding validators
Section titled “Adding validators”Pass an array of validators as the second element in the control tuple:
import { FormBuilder, Validators } from '@angular/forms';
filterForm = this.fb.group({ query: ['', [Validators.required, Validators.minLength(2)]], year: [''], genre: ['']});Built-in validators:
| Validator | What it checks |
|---|---|
Validators.required | Value is not empty |
Validators.minLength(n) | Value is at least n characters |
Validators.maxLength(n) | Value is at most n characters |
Validators.min(n) | Numeric value is at least n |
Validators.max(n) | Numeric value is at most n |
Validators.email | Value is a valid email format |
Validators.pattern(regex) | Value matches the regex |
Accessing control state
Section titled “Accessing control state”Every FormControl tracks its validation state. You can read this state in the template:
// Access a controlget queryControl() { return this.filterForm.get('query');}Control state properties:
| Property | What it means |
|---|---|
control.valid | All validators pass |
control.invalid | At least one validator fails |
control.dirty | The user has changed the value |
control.touched | The user has focused and then blurred the field |
control.hasError('errorName') | A specific error is present |
Displaying errors in the template
Section titled “Displaying errors in the template”Show errors only after the user has interacted with the field (use touched or dirty to avoid showing errors before they type):
<!-- search form template --><form [formGroup]="filterForm"> <input formControlName="query" placeholder="Search..." />
@if (filterForm.get('query')?.invalid && filterForm.get('query')?.touched) { <div class="errors"> @if (filterForm.get('query')?.hasError('required')) { <p class="error">Search query is required.</p> } @if (filterForm.get('query')?.hasError('minlength')) { <p class="error">Enter at least 2 characters.</p> } </div> }
<button type="submit" [disabled]="filterForm.invalid">Search</button></form>A cleaner pattern with a getter
Section titled “A cleaner pattern with a getter”Reading filterForm.get('query') repeatedly is verbose. Use a getter in the class:
get queryControl() { return this.filterForm.controls.query;}Then in the template:
@if (queryControl.invalid && queryControl.touched) { @if (queryControl.hasError('required')) { <p class="error">Required.</p> } @if (queryControl.hasError('minlength')) { <p class="error">Minimum 2 characters.</p> }}Disabling the submit button
Section titled “Disabling the submit button”Use [disabled]="filterForm.invalid" to prevent form submission until all controls are valid:
<button type="submit" [disabled]="filterForm.invalid">Search</button>In CinemaVault, the Browse filter form does not use a submit button — changes are applied immediately via valueChanges. But the search form in the NavBar validates that the query is not blank before navigating.
Exercise
Section titled “Exercise”- Add
Validators.requiredandValidators.minLength(2)to aquerycontrol. - Add a getter
get query() { return this.myForm.controls.query; }. - In the template, show an error paragraph when the control is
invalid && touched. - Show different messages for
requiredandminlengtherrors. - Add
[disabled]="myForm.invalid"to the submit button. - Tab through the input without typing and confirm the error appears on blur.
- Add validators as the second element in the control tuple:
['', [Validators.required, Validators.minLength(2)]]. - Built-in validators include
required,minLength,maxLength,email, andpattern. - Check validation state with
control.invalid,control.touched, andcontrol.hasError('errorName'). - Show errors only after
touchedordirtyto avoid alarming users before they interact. - Use getters in the class to avoid repeating
filterForm.get('fieldName')in the template.