Angular 2 Form Validation
DISCLAIMER
This example is not up to date for Angular v4. I’m planning to update the example soon.
Angular 2 has reached beta, it’s time to explore the framework. In my opinion one of the best new features is the way Angular 2 handles forms. It doesn't seem to be getting a lot of attention and in this post I want to explain you how to implement the following:
- Built-in form validation
- Custom form validation
- Asynchronous form validation
- Combining multiple validators
Let’s start with the basics
Angular 2 forms work with two main components: Controls and Control Groups
Controls
“ Controls have values and validation state, which is determined by an optional validation function.”
A Control can be bound to an input element, and takes 3 arguments (all optional); a default value, a validator and a asynchronous validator.
For example
this.username = new Control('Default value', Validators.required, UsernameValidator.checkIfAvailable);
Which can be used in your HTML using the “ngControl” directive
<input required type=”text” ngControl=”username” />
Control Groups
Defines a part of a form, of fixed length, that can contain other controls.
Multiple Controls can be used to create a Control Group. We use Angular’s “FormBuilder” Class to combine multiple Controls.
class App {
name: Control;
username: Control;
email: Control;
form: ControlGroup;
constructor(private builder: FormBuilder) {
this.name = new Control('', Validators.required);
this.email = new Control('', Validators.required);
this.username = new Control('', Validators.required);
this.form = builder.group({
name: this.name,
email: this.email,
username: this.username
});
}};
The ControlGroup can be used in your HTML, bound to your form, using the ngFormModel directive.
<form [ngFormModel]=”form”>
Built-in form validation
Angular 2 provides three out of the box validators which can either be applied using the “Control” Class, or using HTML properties.
(For example: <input required> will automatically apply the required validator)
- Required
- minLength
- maxLength
Apply them to the Control using the second parameter.
this.name = new Control('', Validators.minLength(4));
And use the following HTML to show the related error message
<input required type=”text” ngControl=”name” /><div *ngIf=”name.dirty && !name.valid”>
<p *ngIf=”name.errors.minlength”>
Your name needs to be at least 4 characters.
</p>
</div>
Custom form validation
Of course we can also write our own custom validators. Here is an example of a validator that checks if the first character is not a number:
interface ValidationResult {
[key:string]:boolean;
}class UsernameValidator {
static startsWithNumber(control: Control): ValidationResult {
if ( control.value !=”” && !isNaN(control.value.charAt(0)) ){
return { “startsWithNumber”: true };
}
return null; }
}
One weird thing you might notice is that returning null actually means the validation is valid. If we find a number at the first position of the string we return the validation error { “startsWithNumber”: true }
Now we can use this Validator in our Control
this.name = new Control('', UsernameValidator.startsWithNumber);
And in our HTML
<input required type=”text” ngControl=”name” /><div *ngIf=”name.dirty && !name.valid”>
<p *ngIf=”name.errors.startsWithNumber”>
Your name can't start with a number
</p>
</div>
Asynchronous form validation
If we want to check something using a Promise (such as fetching data from the server) we can use an asynchronous validator. It works quite similar to the default Validator, only this time we want to return a promise.
class UsernameValidator {
…
static usernameTaken(control: Control): Promise<ValidationResult> {
let q = new Promise((resolve, reject) => {
setTimeout(() => {
if (control.value === ‘David’) {
resolve({“usernameTaken”: true});
} else {
resolve(null);
}
}, 1000)
});
return q;
}
}
This time we want to use the third parameter of the Control to apply the validator.
this.name = new Control('', UsernameValidator.startsWithNumber, UsernameValidator.usernameTaken);
In our HTML we wan’t to show 2 different states, when the Promise is pending and the validation message if needed.
<input required type=”text” ngControl=”name” /><p *ngIf=”name.pending”>
Fetching data from the server...
</p><div *ngIf=”name.dirty && !name.valid && !name.pending”>
<p *ngIf=”name.errors.startsWithNumber”>
Your name can't start with a number
</p> <p *ngIf=”name.errors.usernameTaken”>
This username is already taken
</p>
</div>
Combining multiple validators
This last part is very easy, Angular 2 provides two methods to combine Validators. Validators.compose and Validators.composeAsync. Both take an Array of validators.
this.name = new Control('', Validators.compose([Validators.required, Validators.minLength(4)]));
Example code
You can find the working example here