Angular 2 Forms – Custom Validators

In this article we are not discussing how to develop forms in Angular 2, we are going to discuss how to write a custom validator for Angular 2 forms. Forms API in Angular 2 has some built-in validators already, here is an model driven form example with built-in required validator.

Initial structure of the application:

app structure

app/app.component.ts


import {Component} from '@angular/core';
import {ControlGroup, FormBuilder, Validators} from '@angular/common';
import {CustomValidators} from './CustomValidators';

@Component({
    selector: 'model-driven-forms-app',
    templateUrl:'/app/app.component.html',
    styles:[`
        .ng-valid {
          border: 1px solid #42A948;
        }
        .ng-invalid {
            border: 1px solid #a94442;
        }
    `]
})
export class AppComponent {

    loginForm: ControlGroup;

    constructor(fromBuilder: FormBuilder){

        this.loginForm = fromBuilder.group({
            email     : ['', Validators.required],
            password  : ['', Validators.required]
        });
    }

    onSubmit() {
        if(this.loginForm.valid) {
            console.log(this.loginForm);
        }
    }
}


app/app.component.html


<div class="container">
    <h3>Login Form</h3>
    <form [ngFormModel]="loginForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
            <label for="email">Email</label>
            <input class="form-control" type="email"
                   id="email" [ngFormControl]="loginForm.controls['email']" />
        </div>
        <div class="form-group">
            <label for="password">Password</label>
            <input class="form-control" type="password"
                   id="password" [ngFormControl]="loginForm.controls['password']" />

        </div>
        <input class="btn btn-default" type="submit"
               value="Save" [disabled]="!loginForm.valid" />
    </form>
</div>


app/main.ts


import {bootstrap} from '@angular/platform-browser-dynamic';
import {AppComponent} from './app.component';

bootstrap(AppComponent);

index.html


<!DOCTYPE html>
<html>
<head>
    <title>Angular 2 Model Driven Forms</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">

    <!-- 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <model-driven-forms-app>
        Loading...
    </model-driven-forms-app>
</body>
</html>


output in the browser:

form1

If we observer the output both the textboxes are red because both are required and there state is invalid, lets enter some data into them.

form2

Now both the textboxes are green, because we entered some data there state became valid. But there is one odd thing, we entered some text which not in correct email format. Still the textbox control state is valid, because we added only required validator it will not validate email format. Lets build custom validator to do that.

In Angular 2 validator is a function which accepts Control as input parameter and returns StringMap<string, boolean>. Where the key is “error code” and the value is true if it fails.

Lets define our custom validation function in CustomValidators class under app folder.

app/CustomValidators.ts


import {Control} from '@angular/common';

export class CustomValidators {

    static emailValidator(control: Control) {
        if (!control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) {

            return { 'invalidEmailAddress': true };
        }
    }
}

Above emailValidator function accepting input control as parameter, then comparing input value against email regular expression. If the value doesn’t match the regular expression then we are returning an error object.

No we need to use this custom validator with our email control, but we already have required validator. To use multiple validators on single control we should use Validators.compose() function.

app/app.component.ts


import {Component} from '@angular/core';
import {ControlGroup, FormBuilder, Validators} from '@angular/common';
import {CustomValidators} from './CustomValidators';

@Component({
    selector: 'model-driven-forms-app',
    templateUrl:'/app/app.component.html',
    styles:[`
        .ng-valid {
          border: 1px solid #42A948;
        }
        .ng-invalid {
            border: 1px solid #a94442;
        }
    `]
})
export class AppComponent {

    loginForm: ControlGroup;

    constructor(fromBuilder: FormBuilder){

        this.loginForm = fromBuilder.group({
            email     : ['', Validators.compose([Validators.required, CustomValidators.emailValidator])],
            password  : ['', Validators.required]
        });
    }

    onSubmit() {
        if(this.loginForm.valid) {
            console.log(this.loginForm);
        }
    }
}


Now even if enter value into email field it will not become green, until we enter email address in correct format. We can build any custom validator using similar approach.

For intial setup and source code @ github

2 thoughts on “Angular 2 Forms – Custom Validators

  1. I get an error when trying to use the email validator: TypeError: Cannot read property ‘match’ of undefined

Leave a Reply