Writing custom validators in Angular


In this blogpost I want to show you how you are writing custom validators in Angular.

https://github.com/FabianGosebrink/AngularJS-Angular-Tutorial-Examples/tree/master/Angular-Tutorial/08_Forms/end/app/validators

Updated to new Syntax

When dealing with forms in Angular you can use the build-in validators like: required, minLength or maxLength, pattern… . Sooner or later you need a custom validator fulfilling a special validation.

Let’s have a form first:

<form #f="ngForm">

    <div class="form-group">
        <label for="calories">Calories</label>
        <input type="text" class="form-control" id="calories" placeholder="Calories" [(ngModel)]="foodItem.calories"
             name="calories">
   
    </div>
    <button type="button" class="btn btn-default" (click)="AddOrUpdateFood()" [disabled]="!f?.valid">Submit</button>
</form>

We see a form which has a variable “f” representing the form with which we can ask for the current state. We also have an input control in which we can add a number - calories in this case.

I know that we could change the type of the input to number but in this post we want to check if whether the things the user typed are number or not.

So lets write a validator which exactly checks for that:

import { Directive, forwardRef, Attribute } from '@angular/core';
import { Validator, FormControl, NG_VALIDATORS } from '@angular/forms';

@Directive({
    selector: '[isNumber][formControlName],[isNumber][formControl],[isNumber][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => IsNumberValidator), multi: true }
    ]
})

export class IsNumberValidator implements Validator {

    validate(c: FormControl): { [key: string]: any } {

        if (isNaN(+c.value)) {
            // console.log(c.value + " is not a number");
            return {
                isNumber:
                {
                    valid: false
                }
            };
        }

        // console.log(c.value + " is a number");
        return null;
    }
}

This validator is named “IsNumberValidator” and has a function with a FormControl as a parameter. After the imports we define a directive here and adding the selectors we want. The “providers” let us extend the build-in NG_VALIDATORS and add our new validator. The class implements the validator class with the “validate”-Method which takes a FormControl and returns null if everything is okay and not null if the validation fails.

Before we can use it in our form we have to include it in our module. I built a shared module which I include in my app.module.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { IsNumberValidator } from '../validators/isNumber.validator';
import { IsInRangeValidator } from '../validators/isInRange.validator';

@NgModule({
    imports: [
        // Modules
        BrowserModule
    ],

    declarations: [

        // Components &amp; directives

        IsNumberValidator,
         IsInRangeValidator
    ],

    providers: [
        // Services
    ],

    exports: [
        IsNumberValidator,
        IsInRangeValidator
    ]
})

export class SharedModule { } 

and in our app.module:

// ...

import { SharedModule } from './modules/shared.module';


@NgModule({
    imports: [
        // ...
        SharedModule
    ],

    declarations: [
        // ...
    ],

    providers: [
        // ...
    ],

    bootstrap: [AppComponent]
})

export class AppModule { } 

We can use it in the form like

<form #f="ngForm">
    <div class="form-group">
        <label for="calories">Calories</label>
        <input type="text" class="form-control" id="calories" placeholder="Calories" [(ngModel)]="foodItem.calories"
            isNumber name="calories">
   
    </div>
    <button type="button" class="btn btn-default" (click)="AddOrUpdateFood()" [disabled]="!f?.valid">Submit</button>
</form> 

Here we add the validator to the input field of our form. It is only a directive so we can treat it this way like we did with every directive before.

So now the validator directive fires everytime a key is hit. We can now add a

... #calories="ngModel" ...

to the form and check if the variable we introduced is valid with

... calories.valid ...

<form #f="ngForm">
    <div class="form-group">
        <label for="calories">Calories</label>
        <input type="text" class="form-control" id="calories" placeholder="Calories" [(ngModel)]="foodItem.calories"
            isNumber name="calories" #calories="ngModel">
   
    <div *ngIf="!calories.valid" class="alert alert-danger">
            Field is not valid
        </div>
   
    </div>
    <button type="button" class="btn btn-default" (click)="AddOrUpdateFood()" [disabled]="!f?.valid">Submit</button>
</form> 

But it would be nice to show specific error messages to the users and not only if the filed is valid or not. Combining the variable with the dirty-check and our validator-naming we can show the user messages exactly to what was going wrong in the form.

<form #f="ngForm" novalidate>
    <div class="form-group">
        <label for="calories">Calories</label>
        <input type="text" class="form-control" id="calories" placeholder="Calories" [(ngModel)]="currentFood.calories" required
            isNumber name="calories" #calories="ngModel"> 
            
        <div *ngIf="calories.errors?.required &amp;&amp; (calories?.dirty &amp;&amp; !f.submitted)" class="alert alert-danger">
            *
        </div>

        <div *ngIf="calories.errors?.isNumber &amp;&amp; (calories?.dirty &amp;&amp; !f.submitted)" class="alert alert-danger">
            Please enter a number in a valid range
        </div>
    </div>
    <button type="submit" class="btn btn-default" (click)="AddOrUpdateFood()" [disabled]="!f?.valid">Submit</button>
</form>

And that’s basically it.

Angular2CustomValidators

HTH

Regards https://github.com/FabianGosebrink/AngularJS-Angular-Tutorial-Examples/tree/master/Angular-Tutorial/08_Forms/end/app/validators

http://blog.thoughtram.io/angular/2016/03/14/custom-validators-in-angular-2.html

Thanks to Jürgen Gutsch for doing a review of this blogpost.

Fabian