Angular Form Validation Examples
Angular provides a number of built in directives for easy form validation. This tutorial demonstrates Angular form validation using both template driven and reactive (model driven) forms.
If you are new to Angular forms, check out this introduction first.
Angular Form Validation Examples:
Template Driven Approach:
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user = {name:""}
onSubmit(){
console.log("form submitted!");
}
}
app.component.html
<form #userForm="ngForm" (ngSubmit)="onSubmit()">
<label>Name</label>
<input type="text" #name="ngModel" [(ngModel)] = "user.name" name="name" required minlength="2"/>
<button type="submit" [disabled]="!userForm.form.valid">Submit</button>
</form>
<div *ngIf="name.invalid">
<div *ngIf= "name.errors.required">
Input is required!
</div>
<div *ngIf = "name.errors.minlength">
Input must be at least 2 characters.
</div>
</div>
Using the template driven approach, we can leverage native HTML validation attributes to create "automagic" form validation. Notice how we've added required and minlength="2" to our single <input/> field.
With the template approach, Angular automatically creates FormControl instances for every input and applies built in Validator functions for you.
This allows us to conditionally show error messages based on the status of the input field. After creating a template reference #name="ngModel", we are able to show/hide DOM elements like name.errors.required and name.errors.minlength.
Reactive Approach
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user = {name:""}
userForm = new FormGroup({
"name": new FormControl('', [
Validators.required,
Validators.minLength(2)
])
})
onSubmit(){
console.log("form submitted!");
}
}
app.component.html
<form [formGroup] = "userForm" (ngSubmit) = "onSubmit()">
<label>Name</label>
<input type="text" formControlName = "name" />
<button type="submit" [disabled]="!userForm.valid" >Submit</button>
</form>
<div *ngIf="userForm.invalid">
<div *ngIf= "userForm.controls.name.errors.required">
Input is required!
</div>
<div *ngIf = "userForm.controls.name.errors.minlength">
Input must be at least 2 characters.
</div>
</div>
The reactive approach behaves the same way as the previous example. The main difference is moving logic from the template to the controller class. Instead of specifying required and minlength in the template, we specify Validator functions on the FormControl instances in app.component.ts.
Angular Form Validation On Submit
In our last example, we conditionally enable the submit button based on validation state. What if we want to validate the inputs only after a user hits "submit"?
Validating user input on submit is a common practice. Let's demonstrate how to validate user input on submit using both the template driven and reactive approach:
Template Driven Approach:
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user = {name:""}
onSubmit(form){
if(form.valid){
console.log("form is valid")
} else {
console.log(form.controls.name.errors)
}
}
}
app.component.html
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<label>Name</label>
<input type="text" #name="ngModel" [(ngModel)] = "user.name" name="name" required minlength="2"/>
<button type="submit">Submit</button>
</form>
Remember that Angular "automagically" creates an instance of NgForm using the template driven approach. Since we don't explicitly define the form instance in our controller, we must pass it from the view. This allows us to get the current state of the form within our onSubmit() function in app.component.ts
To do this, notice how we pass in our #userForm template reference to onSubmit(userForm). This allows us to inspect the form's current state from within the controller class function onSubmit().
For this example, we simply log "form is valid" if form.valid. Otherwise, we log the errors associated with our form.controls.name instance.
Reactive Approach:
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
userForm = new FormGroup({
"name": new FormControl('', [
Validators.required,
Validators.minLength(2)
])
})
onSubmit(){
if(this.userForm.valid){
console.log("form is valid")
} else {
console.log(this.userForm.controls.name.errors)
}
}
}
app.component.html
<form [formGroup] = "userForm" (ngSubmit) = "onSubmit()">
<label>Name</label>
<input type="text" formControlName = "name" />
<button type="submit">Submit</button>
</form>
Since the reactive approach defines the FormGroup instance in the controller, we don't have to pass in the form instance like we did with the template driven example.
Notice how we directly reference this.userForm without passing any arguments to our onSubmit() function.
Custom Validator Example
You've seen how we can leverage HTML attributes (required, minlength) and built in validators (Validators.required, Validators.minLength()) to validate user inputs. But what if we want to provide custom validations? For example, what if a name input can't equal specific values like 'Bob' or 'Joe'?
We can implement our own validator functions for these cases. Let's demonstrate how to add a customer validator both the template driven and reactive way:
Template Driven Approach:
app.component.html
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<label>Name</label>
<input type="text" #name="ngModel" appInvalidEntry="Joe" [(ngModel)] = "user.name" name="name" required minlength="2"/>
<button type="submit">Submit</button>
</form>
invalid-entry.directive.ts
import { Directive, Input } from '@angular/core';
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS} from '@angular/forms';
@Directive({
selector: '[appInvalidEntry]',
providers: [{provide: NG_VALIDATORS, useExisting: InvalidEntryDirective, multi: true}]
})
export class InvalidEntryDirective implements Validator {
@Input('appInvalidEntry') invalidEntry: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.invalidEntry? invalidEntryValidator(new RegExp(this.invalidEntry, 'i'))(control)
: null;
}
}
export function invalidEntryValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const invalid = nameRe.test(control.value);
return invalid ? {'invalidEntry': {value: control.value}} : null;
}
}
Adding a custom validator the template driven way doesn't require any changes to app.component.ts. Instead, we create a custom directive that implements the Validator interface. We then reference this directly from the template via appInvalid="Joe"
Reactive Approach:
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
import { invalidEntryValidator } from './invalid-entry.directive'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
userForm = new FormGroup({
"name": new FormControl('', [
Validators.required,
Validators.minLength(2),
invalidEntryValidator(/Joe/i)
])
})
onSubmit(){
if(this.userForm.valid){
console.log("form is valid")
} else {
console.log(this.userForm.controls.name.errors)
}
}
}
app.component.html
<form [formGroup] = "userForm" (ngSubmit) = "onSubmit()">
<label>Name</label>
<input type="text" formControlName = "name" />
<button type="submit">Submit</button>
</form>
Notice how we utilize the exact same directive from the previous example. By importing the invalidEntryValidator from invalid-entry.directive.ts, We can reference invalidEntryValidator directly in the controller class.
Notice how we add the validator to the same list of Validator functions within the FormControl definitions.
Cross Field Validation Example
Let's say we have two inputs: one for the user's first name and one for their last name. We can create a cross field custom validator function to validate that the user's first name does not match their last name:
The Template Driven Approach
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user = {name:""}
onSubmit(form){
if(form.valid){
console.log("form is valid")
} else {
console.log(form.errors)
}
}
}
app.component.html
<form #userForm="ngForm" appInvalidEntry (ngSubmit)="onSubmit(userForm)">
<label>First</label>
<input type="text" #name="ngModel" [(ngModel)] = "user.first" name="first" required minlength="2"/>
<label>Last</label>
<input type="text" #age="ngModel" [(ngModel)] = "user.last" name="last" />
<button type="submit">Submit</button>
</form>
invalid-entry.directive.ts
import { Directive, Input } from '@angular/core';
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, FormGroup, ValidationErrors} from '@angular/forms';
@Directive({
selector: '[appInvalidEntry]',
providers: [{provide: NG_VALIDATORS, useExisting: InvalidEntryDirective, multi: true}]
})
export class InvalidEntryDirective implements Validator {
@Input('appInvalidEntry') invalidEntry: string;
validate(control: AbstractControl): ValidationErrors {
return matchingInputValidator(control)
}
}
export const matchingInputValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const first = control.get('first');
const last = control.get('last');
return first && last && first.value == last.value ? { 'matchingInputs': true } : null;
};
Notice how we've tweaked our directive to include a matchingInputValidator validator function. Unlike the previous example, this function takes a FormGroup as a control input. This is because we need the context of the form as a whole to perform cross validation on multiple fields within the form.
For these reasons, we add appInvalidEntry to the <form> tag in our app.component.html file.
The Reactive Approach
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
import { matchingInputValidator } from './invalid-entry.directive'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
userForm = new FormGroup({
"first": new FormControl('', [
Validators.required,
Validators.minLength(2)]),
"last": new FormControl('')
}, matchingInputValidator)
onSubmit(){
if(this.userForm.valid){
console.log("form is valid")
} else {
console.log(this.userForm.errors)
}
}
}
app.component.html
<form [formGroup] = "userForm" (ngSubmit) = "onSubmit()">
<label>Name</label>
<input type="text" formControlName = "first" />
<label>Last</label>
<input type="text" formControlName = "last" />
<button type="submit">Submit</button>
</form>
Notice how we've imported the matchingInputValidator Validator function from invalid-entry.directive.ts. Since our validator is on the FormGroup instance itself, we add it as a second argument like FormGroup({}, matchingInputValidator)
Async Validator Example
The previous examples have all worked with synchronous validators. This means validation occurs immediately on the control instances.
Sometimes you want to perform async validation. A common use case is checking the database to make sure a user with that particular name doesn't already exist...:
The Template Driven Approach
invalid-entry.directive.ts
import { Directive, Input } from '@angular/core';
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, FormGroup, ValidationErrors, AsyncValidator} from '@angular/forms';
import { UserService } from './user.service'
import { Observable } from 'rxjs'
import { catchError, map, tap } from 'rxjs/operators';
@Directive({
selector: '[appInvalidEntry]',
providers: [{provide: NG_VALIDATORS, useExisting: InvalidEntryDirective, multi: true}]
})
export class InvalidEntryDirective implements AsyncValidator {
constructor(private userService: UserService){
}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.userService.nameExists(control.value).pipe(
map(nameExists => (nameExists ? { nameExists: true } : null)),
catchError(() => null)
);
}
}
Notice how we've modified the validate() function in our invalid-entry.directive.ts based on the eventual completion of a mock userService. Our directive now implements the AsyncValidator interface.
Nothing will change in both app.component.ts and app.component.html for this example.
The Reactive Approach
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
import { InvalidEntryAsync } from './invalid-entry.directive'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private invalidEntry: InvalidEntryAsync) {}
userForm = new FormGroup({
"first": new FormControl('', [
Validators.required,
Validators.minLength(2)]),
"last": new FormControl('')
}, {asyncValidators: [this.invalidEntry.validate.bind(this.invalidEntry)]})
onSubmit(){
if(this.userForm.valid){
console.log("form is valid")
} else {
console.log(this.userForm.errors)
}
}
}
app.component.html
<form [formGroup] = "userForm" (ngSubmit) = "onSubmit()">
<label>Name</label>
<input type="text" formControlName = "first" />
<label>Last</label>
<input type="text" formControlName = "last" />
<button type="submit">Submit</button>
</form>
invalid-entry.directive.ts
import { Injectable, Input, Directive } from '@angular/core';
import { ValidatorFn, AbstractControl, Validator, NG_VALIDATORS, FormGroup, ValidationErrors, AsyncValidator} from '@angular/forms';
import { UserService } from './user.service'
import { Observable } from 'rxjs'
import { catchError, map, tap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class InvalidEntryAsync implements AsyncValidator {
constructor(private userService: UserService){}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.userService.nameExists(control.value).pipe(
map(nameExists => (nameExists ? { alreadyExists: true } : null)),
catchError(() => null)
);
}
}
@Directive({
selector: '[appInvalidEntry]',
providers: [{provide: NG_VALIDATORS, useExisting: InvalidEntryDirective, multi: true}]
})
export class InvalidEntryDirective implements AsyncValidator {
constructor(private userService: UserService){}
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.userService.nameExists(control.value).pipe(
map(nameExists => (nameExists ? { alreadyExists: true } : null)),
catchError(() => null)
);
}
}
Notice how we've added an InvalidEntryAsync class to our invalid-entry.directive.ts file. While this is nearly identical to the InvalidEntryDirective class, the @Injectable annotation allows us to reference the class directly in our app.component.ts
In our controller class, we specify any async validators after we've defined our FormControl instances within the FormGroup.
Conclusion
Angular provides directives for easily implementing form validation. You can leverage these directives through both the template driven and reactive (model driven) approach.
Confused? Be sure to check out our intro to observables and creating forms with Angular for more information.