Angular 2 Template Driven Validation
Last week’s post covered model driven validation in Angular 2 and this week I will be looking at template driven validation. Template driven validation provides a simplified way to add validation without the need to directly use a control group. This is a great way to get up and running for simple form cases, and maybe more complex ones as well. I haven’t hit a case yet that wouldn’t work with either validation style.
Building a Template Form with Validation
I started with a simple form that contains labels and inputs for entering a name and email address that are bound to corresponding values in the associated model.
<form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" [(ngModel)]="name"> </div> <div class="form-group"> <label for="name">Email</label> <input type="text" class="form-control" [(ngModel)]="email"> </div> </form>
The next example is the same form with validation added.
<form #contactForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" ngControl="name" #nameControl="ngForm" [(ngModel)]="name" required minlength="3"> <div [hidden]="nameControl.valid || !nameControl.errors || !nameControl.errors.required">Name is required</div> <div [hidden]="nameControl.valid || !nameControl.errors || !nameControl.errors.minlength">Min Length</div> </div> <div class="form-group"> <label for="name">Email</label> <input type="text" class="form-control" ngControl="email" #emailControl="ngForm" [(ngModel)]="email" required> <div [hidden]="emailControl.valid || !emailControl.errors || !emailControl.errors.required">Email is required</div> </div> </form>
The first change you will notice is that the form now has a name assigned using #contactForm=”ngForm”. In this case the name is not used, but it could be used to disable a submit button if any of the inputs the form contained didn’t have valid values. I am sure there are a lot more use cases as well.
On the inputs that need validation a controls name is assigned via ngControl=”name” and a name is assigned using #nameControl=”ngForm”. I had some trouble withe assigning names to my inputs at first I keep getting the following.
EXCEPTION: Cannot assign to a reference or variable!
The cause of the issue was that I was trying to use #name for the control name which was already a property on the model. Once I changed to #nameControl all worked fine.
For the input for name above required minlength=”3″ tells the Angular form that the input is required and must be at least three characters in length.
Displaying Validation
For the name input you can see that it has two divs used to show specific messages based on which validation condition failed.
<div [hidden]="nameControl.valid || !nameControl.errors || !nameControl.errors.required">Name is required</div> <div [hidden]="nameControl.valid || !nameControl.errors || !nameControl.errors.minlength">Min Length</div>
I am not sure why, but when using the template style validation I had to check !nameControl.errors in addition to the specific error like !nameControl.errors.required or I would get an exception when the page tried to load. If your control doesn’t have more than a single validation then nameControl.valid would be a sufficient condition for showing validation messages.
Custom Validators
I am going to use the same email validator from last week with a few changes so can be used from a template. The following is the full class.
import {Control, NG_VALIDATORS, Validator} from '@angular/common'; import {Directive, Provider, forwardRef} from '@angular/core'; const EMAIL_VALIDATOR = new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => EmailValidator), multi: true }); @Directive({ selector: '[emailValidator]', providers: [EMAIL_VALIDATOR] }) export class EmailValidator implements Validator { validate(control: Control): {[key: string]: any} { const emailRegexp = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; if (control.value !== null && control.value !== "" && (control.value.length <= 5 || !emailRegexp.test(control.value))) { return { "email": true }; } return null; } }
The biggest difference is the @Directive decorator. The selector is the attribute that will be used to mark that an input needs to pass the specified validation. I am not 100% sure of the specifics of the provider, but the above works. This is another topic I need to explore more.
Using a Custom Validator
First in the model the validator must be imported.
import {EmailValidator} from './email-validator';
Then the validator needs to be added to the directives section of the @Component decorator.
@Component({ selector: 'contact-detail', templateUrl: 'contact.detail.html', directives: [ EmailValidator ], providers: [ HTTP_PROVIDERS ] })
To use in the view just add the emailValidator selector to the proper input.
<input type="text" class="form-control" ngControl="email" #emailControl="ngForm" [(ngModel)]="email" required emailValidator>
Finally add a div to display the error.
<div [hidden]="emailControl.valid || !emailControl.errors || !emailControl.errors.email">Email is invalid</div>
Wrapping Up
Using template driven validation you can quickly get a form validated without having to make changes outside of your template (unless you are using a custom validator). Angular 2 offers a lot of options when it comes to validation and where it should be put.
Angular 2 Template Driven Validation Read More »