Creating Custom Validators in Angular for Reactive Forms

Learn how to create and use custom validators in Angular reactive forms.

Photo by Pixabay: https://www.pexels.com/photo/gray-scale-photo-of-gears-159298/

Angular has powerful built-in validators for reactive forms such as required, min, max, email, or pattern. While they are very useful, they do not cover all the cases that you will come up against when building real-world applications. Luckily, the Angular team is aware of this and allowed us to create custom validators to check input in reactive forms. Let’s see how we can do this.

A validator is just a function

The first thing you need to understand is that an Angular validator is just a function with two significant characteristics:

  • It is a function that has a parameter of type AbstractControl

  • It is a function that should return null if the corresponding field is valid

    • And return anything else if not

For example, here is a simple custom validator that checks if a person is over 18 years old.

export function adult(control: AbstractControl) {
  const controlValue = parseInt(control.value);

  if(controlValue >= 18 && controlValue <= 105){
    // input is valid
    return null;
  }
  
  // input is not valid, we can return any object. In our case we return 
  // an object with the adult property set to false
  return {
    adult: false
  }
}

Adding this validator to a field is done like this:

this.personForm= new FormGroup({
      name: new FormControl<string>(''),
      age: new FormControl<number>('', [adult])
});

Notice that we return null to signal that the field is valid. If the field is invalid, we can return any object. That object will get stored in the control’s errors property.

Organizing custom validator functions

The example above is a perfectly usable validator. It is an exported function, which means you can use it throughout your program. However, if you have many custom validators, I think there are better ways to organize them. For example, I like to create a class for them and expose them as static functions.

export class CustomValidators {
  
  // Expose validator as public static function
  static hashTags(control: AbstractControl): { [key: string]: any } {
    if (control.value) {
      // Grab all categories by spliting them after ;
      const categories = control.value.split(';');

      // Check that each category starts with #
      for (let c of categories) {
        if (c && !c.startsWith('#')) {
          return {
            invalidHashTag: true,
          };
        }
      }

      // Validation passed; To signal this we need to return null
      return null;
    }
  }

In the example above, I created a class called CustomValidators which exposes the hashTags input validator. Adding this validator to a form control inside a reactive form is pretty simple: we use the name of the class and the function’s name, just like we do with the built-in validators.

this.blogPostForm = new FormGroup({
      title: new FormControl<string>(''),
      content: new FormControl<string>(''),
      
      categories: new FormControl<string>('', [CustomValidators.hashTags])
});

Custom validators with parameters

What if we want to pass parameters to the validator? We can certainly do that, but there is a catch: we can not add parameters directly in the validator functions. Instead, we need to create a function that will return our validator. Sort of like a validator factory. This “factory” will receive our parameters as input and pass them to a new instance of a custom validator.

Sounds complicated? It won’t be after you take a look at some code :) Let’s augment our hashTags validator and limit the number of hashTags. The user will pass that number as an argument.

// function that receives an argument from the user and 
// creates a validator function
static hashTagsWithMaxSize(maxSize: number): ValidatorFn {
  
  // returns a function which takes an Anstract control as an input
  return (control: AbstractControl): { [key: string]: any } | null => {
    if (control.value) {
      // Grab all categories by spliting them after ;
      const categories = control.value.split(';');

      // Check that each category starts with #
      for (let c of categories) {
        if (c && !c.startsWith('#')) {
          return {
            invalidHashTag: true,
          };
        }
      }

      // Check if max nb exceeded
      if (categories.length > maxSize) {
        return {
          nbHashTagsExceeded: true,
        };
      }

      // Validation passed; To signal this we need to return null
      return null;
    }
  };
}
this.blogPostForm = new FormGroup({
    categories: new FormControl<string>('', 
      [CustomValidators.hashTagsWithMaxSize(2)])
});
Previous
Previous

Four Books Every Aspiring Programmer Should Start Reading Right Now

Next
Next

How to Dynamically Add or Remove Validators in Angular