loading...
Cover image for Custom Validators for Angular Reactive Forms

Custom Validators for Angular Reactive Forms

_adam_barker profile image Adam Barker ・3 min read

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"
    ]
  }
]);

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)
    ]
  }
]);

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

would return:

{
    maxLength: {
        actualLength: 28,
        requiredLength: 20
    }
}

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;
}

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]
});

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!"
        }
      };
    }
  };
}

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"
    ])]
});

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!

Posted on May 24 by:

_adam_barker profile

Adam Barker

@_adam_barker

I'm a developer with (way too) many years of experience, originally from the UK but now in NYC having co-founded a real-estate startup and an AI driven content engine platform.

Discussion

markdown guide