Angular2 Forms Tutorial: Creating Custom Validators

Every now and then developers come across situations where existing validators are not suitable for their needs, if you are facing this situation your best option is to create a custom validatior, to illustrate how you can do that in angular2 I’m going to create a simple custom validator that will verify if the field value starts with a given string, it’ll be something like this:

<input type="text" startWith="ABC" ...>

In this example the attribute startWith is my validator, for this input to be valid it must contain a value that starts with ABC.

Now let’s see how it works, a custom validator is nothing more than a attribute directive that implements the Validator class, before we proceed to the details let’s take a look at the code:

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';
 
@Directive({ 
  selector: '[startWith]',
  providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}] 
})
export class CustomValidatorDirective implements Validator{
  @Input('startWith') expr: string;
 
  validate(control: AbstractControl) {
    if(control.value && !control.value.startsWith(this.expr)){
        return {'startWith': control.value};
    }
    return null;
  }
}

Every validator has a validate() method, angular2 will call it every time the application needs to validate the field, note that it receives an AbstractControl as a parameter, which is the form control itself, this parameter will give us access to all of the field attributes, including the value (the only one we’re going to use in this tutorial). Also note that I’ve declared an @Input, this is how I’m getting the value "ABC" passed to the directive in the example above.

Now that we have the field value and the input we can implement the validate method and check if the field value contains the expression. If the field is valid the function returns null, otherwise it returns the validation error object.

Lastly, before you can use the validator, you have to register it in the app.modules.ts:

...
import { CustomValidatorDirective } from './customValidator.directive';
 
@NgModule({
  declarations: [ ... ,CustomValidatorDirective],
  ...
})
export class AppModule { }

Angular2 Forms Tutorial: Validating Required Fields

Angular2 Form Validation
Angular2 Form Validation

In this tutorial I’ll give you a simple example of how to validate a form with angular2. As you can see in the image, the example consists of a form with two required fields, the form can only be submitted if both fields are valid.

I’m going to use bootstrap for styling, if you want your form to look like the image above you just have to add the follwoing scripts to your index.html:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" >
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>

Now let’s take a look at our component:

import {Component} from '@angular/core';
 
@Component({
  selector: 'form-validation-example',
  template: `
     <form (ngSubmit)="submit()" >
	<div class="form-group has-feedback" 
	     [ngClass]="{ 'has-error' : !firstNameControl.valid && submitAttempt }">
           <label class="control-label" >First Name</label>
           <input type="text" class="form-control"
	      name="firstName" [(ngModel)]="firstName" #firstNameControl="ngModel" required>
           <span *ngIf="!firstNameControl.valid && submitAttempt"
	      class="glyphicon glyphicon-remove form-control-feedback" ></span>
	</div>
	<div class="form-group has-feedback" 
	     [ngClass]="{ 'has-error' : !emailControl.valid && submitAttempt }">
	   <label class="control-label">E-mail</label>
	   <input type="text" class="form-control"
              name="email" [(ngModel)]="email" #emailControl="ngModel" required>
           <span *ngIf="!emailControl.valid && submitAttempt"
	      class="glyphicon glyphicon-remove form-control-feedback" ></span>
	</div>
	<button type="submit" class="btn btn-default" (click)=initSubmit()>Submit</button>
     </form>
  `
})
export class FormComponent {
      submitAttempt = false;
 
      initSubmit(){
	  this.submitAttempt = true;
      }
 
      submit(){
 	  console.log('success!');
      }
}

This is just a regular form, what makes the fields required is the required attribute at the end of each input, just using this attribute will already be enough to prevent the form from submitting, but it won’t change the field style as shown in the demo at the beginning.

To be able to change the style of the input we first have to know if the field is valid or not, an easy way to achieve this is to declare a template reference variable for each field, if you take a look at the code you’ll see the attribute #firstNameControl="ngModel" on the first input and #emailControl="ngModel" on the second, that’s how template reference variables are declared. Now I can use the names firstNameControl and emailControl to access properties of my inputs on other parts of my template, but the only one that matters to us is the property valid.

We also need a way to determine if the submit button was clicked or not, that’s why I’ve created the variable submitAttempt, it’s false by default but it’ll be set to true once the button is clicked.

By using the template reference variables and the submitAttempt variable we can now write the condition that changes the styles, which is !firstNameControl.valid && submitAttempt for the first field and !emailControl.valid && submitAttempt for the second. These conditions are used to add the bootstrap class has-error on each form-group and also to render the icon at the end of the input.

Lastly we have the submit() function which will just print the string ‘success!’ on the console, this function will only be called if both fields are valid.