Angular Reactive Forms – Building Custom Validators

| By Maina Wycliffe | | | Angular

Angular has built-in input validators for common input validation such as checking min and max length, email etc.  These built-in validators can only take you so far, at some point you might need to build a custom validator. In this post, I will show you how to build your own custom validators for input and a validator to check whether password and confirm password match before submitting. This will help you be able to build custom validators for both form group and specific form controls. So, without further ado, lets get started:

How it will work

I am going to assume that you have some prior knowledge on Reactive Forms in Angular and using built-in validators. If not, please checkout this page for a quick tutorial on Reactive Forms. We are going to have three custom validators. The first validator will check whether the current user is over the age of 18 and under the Age of 60. While the second validator will check whether the email provider is theinfogrid.com (this blogs domain name). The last validator will check to see the password and confirm password are a match.

Building a Custom Validator

We are going to create a class known as CustomValidators, which will house our validator functions. This will allow us to access all our validator functions under a single class.

ng generate class custom-validator

Next, we need to first create validators for the first two cases – AgeLimit and EmailDomain Validators. We are going to add the methods as static methods inside our CustomValidator class. The names of our validator classes shall have the term Validator appended. So that, AgeLimit becomes AgeLimitValidator and EmailDomain becomes EmailDomainValidator. Each of our two methods will return a Validator Function (ValidatorFN) after evaluating whether the control value is valid or not. In case where the value is valid, the validator function shall return a null value and an object if invalid. The object shall have a name and a true value i.e.

{ ageLimit: true  } or  { emailDomain: true }

AgeLimitValidator Function

The first validator – AgeLimitValidator – shall look like this:

NB: Follow the comments inside the code for further clarifications.

static ageLimitValidator(minAge: number, maxAge: number): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } | null => {
      // if control value is not null and is a number
      if (control.value !== null) {
        // return null  if it's in between the minAge and maxAge and is A valid Number
        return isNaN(control.value) || // checks if its a valid number
        control.value < minAge || // checks if its below the minimum age
          control.value > maxAge // checks if its above the maximum age
          ? { ageLimit: true } // return this incase of error
          : null; // there was not error
      }
      return null;
    };
  }

It takes in the minimum and maximum age as parameters and uses them to evaluate age limit.

EmailDomainValidator Function

The second validator – EmailDomainValidator – function shall look like this:

NB: Follow the comments inside the code for further clarifications.

static emailDomainValidator(domain: string): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } | null => {
      // if control value is not undefined, then check if the domain name is valid
      if (control.value !== null) {
        const [_, eDomain] = control.value.split('@'); // split the email address to get the domain name
        return eDomain !== domain // check if the domain name matches the one inside the email address
          ? { emailDomain: true } // return in case there is not match
          : null; // return null if there is a match
      }
      return null; // no error, since there was no input
   };
}

Using the Custom Validators

To use your custom validator inside your reactive forms, import the class and call the validator function as you would a built-in validator.

createMyForm(): FormGroup {
    return this.fb.group({
      ...
      age: [
        null,
        Validators.compose([CustomValidator.ageLimitValidator(18, 60)])
      ],
      email: [
        null,
        Validators.compose([
          Validators.email,
          CustomValidator.emailDomainValidator('theinfogrid.com')
        ])
      ],
     ...
    });
  }

As you can see above, you can easily mix your custom validators and built-in validators. Now, inside your template, it’s a matter of checking whether that form control has the specific error and show the error message:

//For Age Limit Validator
<mat-error
  *ngIf="myform.controls['age'].touched && myform.controls['age'].hasError('ageLimit')"
>
  The age limit is between 18 and 60 years!
</mat-error>

//For Email Domain Validator
<mat-error
  *ngIf="myform.controls['email'].touched && myform.controls['email'].hasError('emailDomain')"
>
  Email address must be from theinfogrid.com
</mat-error>

And that’s it, we now have a custom validator for our form controls.

Custom Validators for Forms

The first two validators are for individual controls. What about if you wanted to validate something that involved more than one control. Like, checking whether password and confirmation passwords are a match. Here we will need a validator for our form group instead of individual form controls. Inside our CustomValidator class, let’s add a third validator method for checking whether password match – PasswordMatch – we will call it PasswordMatchValidator. Then we are going to get the password and confirmPassword values from our form and check if they match. Instead of returning a validator function like in the previous two instances, we will instead set an error in our form control for both password and confirmPassword. So, our new validator method will look like this:

NB: Follow the comments inside the code for further clarifications.

static passwordMatchValidator(control: AbstractControl) {
    const password: string = control.get('password').value; // get password from our password form control
    const confirmPassword: string = control.get('confirmPassword').value; // get password from our confirmPassword form control
    // compare is the password math
    if (password !== confirmPassword) {
      // if they don't match, set an error in our confirmPassword form control
      control.get('confirmPassword').setErrors({ NoPassswordMatch: true });
    }
  }

After that, we can add it to the list of our validators of the form group, not the control validators:

return this.fb.group(
  {
    // ...
    password: [null, Validators.compose([Validators.required])],
    confirmPassword: [null, Validators.compose([Validators.required])]
  },
  {
    // our validator for the form group
    validator: CustomValidator.passwordMatchValidator
  }
);

And then you can add the error message in your confirmPassword form control as shown below:

<mat-error
  *ngIf="myform.controls['confirmPassword'].touched && myform.controls['confirmPassword'].hasError('NoPassswordMatch')"
>
  Your password and confirm password do not match
</mat-error>

I hope this post will enable you to create your own custom validators for your own form. You can find the full code on Github.

A Guide for Building Angular 6 Libraries

In an earlier post, I wrote about Angular CLI Workspaces, a new feature in Angular 6 that allows you to have more than one project in a …

Read More
Angular 6 - Angular CLI Workspaces

One of the least talked about features of Angular 6 is Angular CLI Workspaces. Workspaces or Angular CLI Workspaces give angular developers …

Read More
SVG/Webfont Icon Packs for your Angular App

If you are looking to improve your web app both visually and functionally, icons are a very good place to start. Icons if applied correctly, …

Read More
Lazy Loading Scripts and Styles in Angular

In my earlier post, I covered about lazy loading of angular modules. In this post, I will cover lazy loading of scripts and styles, both …

Read More
Working with Environment Variables in Angular 6

It’s common for developers to have multiple application versions that target different environments i.e. development and production. It’s …

Read More
Top VSCode Extensions for Angular Developers

Since the release of VSCode a few years ago, it has become very popular among developers. VSCode is my favorite text editor and use it every …

Read More

Comments