[How-to] Implement custom form validators with Angular

Alain Chautard
Angular Training
Published in
3 min readFeb 16, 2018

--

Form validation is always a hot topic whenever I teach Angular. People usually ask me whether they should use Template driven forms or Reactive forms, and I used to tell them that reactive forms are a better option when you need custom validators.

That was partially wrong though: It is actually fairly easy to write custom validators that work seamlessly with both template-driven and reactive forms, which is what I’m going to show you in this post.

I’m going to create a custom credit card number validator. The validator is going to make sure that a credit card number has 16 digits and that it comes from an accepted credit card company.

In order to perform such validation, we have to implement a validator function that has the following signature:

interface ValidatorFn {    
(c: AbstractControl): ValidationErrors | null
}

That’s the method signature used for both template-driven and reactive forms. The input parameter is a form control, and the output is either an error object or null if the value is valid.

So here is what that method would look like:

static validateCcNumber(control: FormControl): ValidationErrors {
if (! (control.value.startsWith('37')
|| control.value.startsWith('4')
|| control.value.startsWith('5'))
) {
// Return error if card is not Amex, Visa or Mastercard
return {creditCard : 'Your credit card number is not from a supported credit card provider'};
} else if (control.value.length !== 16) {
// Return error if length is not 16 digits
return {creditCard : 'A credit card number must be 16-digit long'};}
// If no error, return null
return null;
}

Now that our validator function is defined, we have to make it applicable to any kind of form.

For template-driven forms, we have to make it a directive that implements a specific interface: Validator.

import { Directive, forwardRef } from '@angular/core';
import { NG_VALIDATORS, AbstractControl, ValidationErrors, Validator, FormControl } from '@angular/forms';
@Directive({
selector: '[validCreditCard]',
// We add our directive to the list of existing validators
providers: [
{ provide: NG_VALIDATORS, useExisting: CreditCardValidator, multi: true }
]
})
export class CreditCardValidator implements Validator {
// This method is the one required by the Validator interface
validate(c: FormControl): ValidationErrors | null {
// Here we call our static validator function
return CreditCardValidator.validateCcNumber(c);
}
static validateCcNumber(control: FormControl): ValidationErrors {
// Here goes the validation code mentioned earlier
}

The above code defines our directive that implements the Validator interface. It also registers that directive as a NG_VALIDATOR, which adds our validator to the collection of existing ones (like required or pattern for instance).

Now I can define a form and make my validator apply to a text input:

<form #form="ngForm" (ngSubmit)="displayData(form.value)">  <input type="text" name="cardnumber" ngModel validCreditCard /></form>

And that’s it! The above form would be invalid as long as the user does not enter a credit card number that passes our validation criteria.

Now let’s add some error handling to render the right error message from our validator function:

<form #form="ngForm" (ngSubmit)="displayData(form.value)">  <input type="text" name="cardnumber" ngModel #cc="ngModel" validCreditCard />  <div *ngIf="cc.dirty && cc.errors?.creditCard" class="error">
{{cc.errors.creditCard}}
</div>
<button type="submit" *ngIf="form.valid">Send info</button></form>

And there you go, with some added CSS styling, that’s what the above code would render when an invalid number is entered:

Why using a static validator function?

That’s a trick of mine so that I can use that function in a reactive form. As a static method, I can easily pass a reference to it as follows:

this.registerForm = this.formBuilder.group({
firstname: ['', Validators.required],
// ...
creditcardnumber: ['', CreditCardValidator.validateCcNumber]
});

You can find the full code for that example in that StackBlitz.

My name is Alain Chautard. I am a Google Developer Expert in Angular, as well as founding consultant and trainer at Angular Training where I help web development teams learn and become fluent with Angular.

If you enjoyed this article, please clap for it or share it. Your help is always appreciated. You can also subscribe to Medium here.

--

--