This tutorial is out of date and no longer maintained.
In this article, we will be exploring Angular 2 built-in and custom validators.
Angular 2 supports a few very useful native validators:
We will build a form to capture user information based on this interface.
// user.interface.ts
export interface User {
username: string; // required, must be 5-8 characters
email: string; // required, must be valid email format
password: string; // required, value must be equal to confirm password.
confirmPassword: string; // required, value must be equal to password.
}
Only show errors message for each field when the field is dirty or form is submitted.
Here is how the UI will look:
View Angular 2 - Custom Validator Directive (final) scotch on plnkr
Here’s our file structure:
|- app/
|- app.component.html
|- app.component.ts
|- app.module.ts
|- equal-validator.directive.ts
|- main.ts
|- user.interface.ts
|- index.html
|- styles.css
|- tsconfig.json
In order to use the new forms module, we need to npm install @angular/forms
npm package and import the latest forms module in the application module.
npm install @angular/forms --save
Here’s the module for our application app.module.ts
:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, FormsModule ], // import forms module here
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
})
export class AppModule { }
Let’s move on to create our app component.
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from './user.interface';
@Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent implements OnInit {
public user: User;
ngOnInit() {
// initialize model here
this.user = {
username: '',
email: '',
password: '',
confirmPassword: ''
}
}
save(model: User, isValid: boolean) {
// call API to save customer
console.log(model, isValid);
}
}
This is how our HTML view will look.
<!-- app.component.html -->
<div>
<h1>Add user</h1>
<form #f="ngForm" novalidate (ngSubmit)="save(f.value, f.valid)">
<!-- we will place our fields here -->
<button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>
</div>
Let’s add our controls one by one.
Requirements: required, must be 5–8 characters
<!-- app.component.html -->
...
<div>
<label>Username</label>
<input type="text" name="username" [ngModel]="user.username"
required minlength="5" maxlength="8" #username="ngModel">
<small [hidden]="username.valid || (username.pristine && !f.submitted)">
Username is required (minimum 5 characters).
</small>
</div>
<pre *ngIf="username.errors">{{ username.errors | json }}</pre>
...
Since required
, minlength
, maxlength
are built-in validators, it’s so easy to use them.
We will only display the error message if username is not valid and the field is touched or form is submitted.
The last line pre
tag is very useful for debugging purposes during development. It displays all the validation errors of the field.
Requirements: required, must be valid email format
<!-- app.component.html -->
...
<div>
<label>Email</label>
<input type="email" name="email" [ngModel]="user.email"
required pattern="^[a-zA-Z0–9_.+-]+@[a-zA-Z0–9-]+.[a-zA-Z0–9-.]+$" #email="ngModel" >
<small [hidden]="email.valid || (email.pristine && !f.submitted)">
Email is required and format should be <i>john@doe.com</i>.
</small>
</div>
...
We set the email to required, then use the built-in pattern validator to test the value with email regex: ^[a-zA-Z0–9_.+-]+@[a-zA-Z0–9-]+.[a-zA-Z0–9-.]+$
.
Requirements:
<!-- app.component.html -->
...
<div>
<label>Password</label>
<input type="password" name="password" [ngModel]="user.password"
required #password="ngModel">
<small [hidden]="password.valid || (password.pristine && !f.submitted)">
Password is required
</small>
</div>
<div>
<label>Retype password</label>
<input type="password" name="confirmPassword" [ngModel]="user.confirmPassword"
required validateEqual="password" #confirmPassword="ngModel">
<small [hidden]="confirmPassword.valid || (confirmPassword.pristine && !f.submitted)">
Password mismatch
</small>
</div>
...
validateEqual
is our custom validator. It should validate the current input value against the password input value.
We will develop a directive for validate equal
.
// equal-validator.directive.ts
import { Directive, forwardRef, Attribute } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
]
})
export class EqualValidator implements Validator {
constructor( @Attribute('validateEqual') public validateEqual: string) {}
validate(c: AbstractControl): { [key: string]: any } {
// self value (e.g. retype password)
let v = c.value;
// control value (e.g. password)
let e = c.root.get(this.validateEqual);
// value not equal
if (e && v !== e.value) return {
validateEqual: false
}
return null;
}
}
The code is quite long, let’s break it down and look into it part by part.
// equal-validator.directive.ts
@Directive({
selector: '[validateEqual][formControlName],[validateEqual]
[formControl],[validateEqual][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
]
})
First, we define directive using the @Directive
annotation. Then we specify the selector
. Selector is mandatory. We will extend the built-in validators NG_VALIDATORS
to use our equal validator in providers
.
// equal-validator.directive.ts
export class EqualValidator implements Validator {
constructor( @Attribute('validateEqual') public validateEqual: string) {}
validate(c: AbstractControl): { [key: string]: any } {}
}
Our directive class should implement the Validator
interface. Validator interface expecting a validate
function.
In our constructor, we inject the attribute value via annotation @Attribute(‘validateEqual’)
and assign it to the validateEqual variable. In our example, the value of validateEqual would be “password”.
// equal-validator.directive.ts
validate(c: AbstractControl): { [key: string]: any } {
// self value (e.g. retype password)
let v = c.value;
// control value (e.g. password)
let e = c.root.get(this.validateEqual);
// value not equal
if (e && v !== e.value) return {
validateEqual: false
}
return null;
}
First, we read the value of our input and assign it to v
. Then, we find the password
input control in our form and assign it to e
. After that, we check for value equality and return errors if it’s not equal.
To use the custom validator in our form, we need to import it to our application module.
// app.module.ts
...
import { EqualValidator } from './equal-validator.directive'; // import validator
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, EqualValidator ], // import to app module
bootstrap: [ AppComponent ],
})
...
Tadaa! Let’s say you type “123” in the password field, then “xyz” in retype the password, it should show you a password mismatch error.
Everything is working fine until you go and change the password value after you’ve entered text in the retype password field.
For example, you type “123” in the password field, then “123” in the retype password field, then change the password input to “1234”. The validation still passes. Why?
It’s because we only apply our equal validator to retype the password. It will trigger only when the retype password value changes.
There are a few ways to fix this. We will discuss one of the solutions here. I’ll leave it to you to figure out the others.
We will reuse our validateEqual
validator and add an attribute call reverse
.
<!-- app.component.html -->
...
<input type="password" class="form-control" name="password"
[ngModel]="user.password"
required validateEqual="confirmPassword" reverse="true">
<input type="password" class="form-control" name="confirmPassword"
[ngModel]="user.confirmPassword"
required validateEqual="password">
...
reverse
is false or not set, we will perform equal validation as explained in the previous section.reverse
is true, we will still perform equal validation, but instead of adding errors to current control, we add errors to the target control.In our example, we set the password
validation reverse to true. Whenever password
is not equal to retype password
, we will insert an error to confirm the password field instead of reseting password field.
The complete custom validator code:
// equal-validator.directive.ts
import { Directive, forwardRef, Attribute } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
]
})
export class EqualValidator implements Validator {
constructor(@Attribute('validateEqual') public validateEqual: string,
@Attribute('reverse') public reverse: string) {
}
private get isReverse() {
if (!this.reverse) return false;
return this.reverse === 'true' ? true: false;
}
validate(c: AbstractControl): { [key: string]: any } {
// self value
let v = c.value;
// control vlaue
let e = c.root.get(this.validateEqual);
// value not equal
if (e && v !== e.value && !this.isReverse) {
return {
validateEqual: false
}
}
// value equal and reverse
if (e && v === e.value && this.isReverse) {
delete e.errors['validateEqual'];
if (!Object.keys(e.errors).length) e.setErrors(null);
}
// value not equal and reverse
if (e && v !== e.value && this.isReverse) {
e.setErrors({ validateEqual: false });
}
return null;
}
}
There are other ways to solve the password and confirm password validation too. Some people suggest to add both password and confirm password in a group (Stack Overflow), then validate it.
There’s really no right or wrong, it’s up to you.
More details:
That’s it. Happy coding!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!