DEV Community

Cover image for Custom Validators for Angular Reactive Forms
Adam Barker
Adam Barker

Posted on

Custom Validators for Angular Reactive Forms

Angular… ugh, I know.

But Reactive Forms are actually pretty cool and once you get the hang of them, you can appreciate all of the form dynamics that Angular handles for you.

At the end of the day we want to supply an object to a form, allow the user to make changes and keep those changes valid.

Angular provides classes to marry object properties to markup and a convenient FormBuilder to help construct groups of form components with default values.

this.formGroup = formBuilder.group([
  {
    "name": [
      "Adam",
    ],
    "age": [
      "21"
    ]
  }
]);
Enter fullscreen mode Exit fullscreen mode

Out of the box Angular provides a set of validators that cover many if not most requirements.

My name should be longer than 20 characters and clearly I’m no older than 25 so I can specify validators to the FormBuilder object:

this.formGroup = formBuilder.group([
  {
    "name": [
      "Adam", Validators.maxLength(20)
    ],
    "age": [
      "21", Validators.max(25)
    ]
  }
]);
Enter fullscreen mode Exit fullscreen mode

We can check for validation errors in our FormGroup object with the errors property of each control. This property maintains an object that, when the value is valid, is empty. Otherwise, the object contains keys indicating how the value has failed validation.

For example, if our name value was say 28 characters, longer than the valid 20 characters,

formGroup.get("name").errors
Enter fullscreen mode Exit fullscreen mode

would return:

{
    maxLength: {
        actualLength: 28,
        requiredLength: 20
    }
}
Enter fullscreen mode Exit fullscreen mode

If we need something a little extra, something outside the typical min, max, required or email, we can write a custom validator.

Suppose we wanted to validate a favorite movie field. Let’s add a movie validator and determined that any value other than Back To The Future is invalid:

function movie(control: AbstractControl)
    : { [key: string]: any } {
    if (control.value !== "Back To The Future") {
        return {
          movie: {
            suppliedMovie: control.value,
            quality: "Questionable"
          }
        };
    }

    return undefined;
}
Enter fullscreen mode Exit fullscreen mode

Here we check the control’s value and it’s not the value we want we can return an object specify how the value is invalid. If the value is valid, we return undefined because we don’t want the errors object to be populated in this case.

It’s a simple change to add our new validator to the FormBuilder call:

this.formGroup = formBuilder.group({
  name: ["Adam", Validators.maxLength(25)],
  age: ["21", [Validators.min(0), Validators.max(25)]],

  // Instead of using the Validators class we can 
  // supply our own validator, movie:
  favoriteMovie: ["Silent Running", movie]
});
Enter fullscreen mode Exit fullscreen mode

What if we wanted to be less stringent and maybe offer the user of our validation function the option to specify a number of movies that could be favorites.

Now we need an argument to movie, like max and maxLength do.

function movie(
  validMovies: string[]
): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } => {
    if (validMovies.indexOf(control.value) == -1) {
      return {
        movie: {
          suppliedMovie: control.value,
          reason: "Not one of my favorites!"
        }
      };
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Instead of the movie function now immediately validating the value and returning an error object, it’s basically a higher-order function and returning a function that Reactive Forms will use. We provide an array of movie names as an argument, and these are used by the function at validation time to check the control’s value.

this.formGroup = formBuilder.group({
  name: ["Adam", Validators.maxLength(25)],
  age: ["21", [Validators.min(0), Validators.max(25)]],

  favoriteMovie: ["Silent Running",
    movie([
        "Teen Wolf", 
        "Saving Private Ryan", 
        "Inception"
    ])]
});
Enter fullscreen mode Exit fullscreen mode

Now, Silent Running (excellent movie, criminally downvoted) is still invalid, but we’ve supplied a list of movies for which the value will be valid.

Check out the StackBlitz for this example!

Latest comments (0)