Angular Reactive Forms – Building Custom Validators

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.