Forms are part of almost every web application out there. Angular strives for making working with forms a breeze. While there are a couple of built-in validators provided by the framework, we often need to add some custom validation capabilities to our application’s form, in order to fulfill our needs.
We can easily extend the browser vocabulary with additional custom validators and in this article we are going to explore how to do that.
TABLE OF CONTENTS
Angular comes with a subset of built-in validators out of the box. We can apply them either declaratively as directives on elements in our DOM, in case we’re building a template-driven form, or imperatively using the
FormBuilder APIs, in case we’re building a reactive forms. If you don’t know what it’s all about with template-driven and reactive forms, don’t worry, we have an articles about both topics here and here.
The supported built-in validators, at the time of writing this article, are:
- required - Requires a form control to have a non-empty value
- minlength - Requires a form control to have a value of a minimum length
- maxlength - Requires a form control to have a value of a maximum length
- pattern - Requires a form control’s value to match a given regex
As mentioned earlier, validators can be applied by simply using their corresponding directives. To make these directives available, we need to import Angular’s
FormsModule to our application module first:
Once this is done, we can use all directives provided by this module in our application. The following form shows how the built-in validators are applied to dedicated form controls:
Or, if we had a reactive form, we’d need to import the
And can then build our form either using
Or use the less verbose
FormBuilder API that does the same work for us:
We would still need to associate a form model with a form in the DOM using the
[formGroup] directive likes this:
Observing these two to three different methods of creating a form, we might wonder how it is done that we can use the validator methods imperatively in our component code, and apply them as directives to input controls declaratively in our HTML code.
It turns out there’s really not such a big magic involved, so let’s build our own custom email validator.
Building a custom validator
In it’s simplest form, a validator is really just a function that takes a
Control and returns either
null when it’s valid, or and error object if it’s not. A TypeScript interface for such a validator looks something like this:
Let’s implement a validator function
validateEmail which implements that interface. All we need to do is to define a function that takes a
FormControl, checks if it’s value matches the regex of an email address, and if not, returns an error object, or
null in case the value is valid.
Here’s what such an implementation could look like:
Pretty straight forward right? We import
@angular/forms to have the type information the function’s signature and simply test a regular expression with the
FormControl’s value. That’s it. That’s a validator.
But how do we apply them to other form controls? Well, we’ve seen how
Validators.required and the other validators are added to the
new FormControl() calls.
FormControl() takes an initial value, a synchronous validator and an asynchronous validator. Which means, we do exactly the same with our custom validators.
Don’t forget to import
validateEmail accordinlgy, if necessary. Okay cool, now we know how to add our custom validator to a form control.
However, what if we want to combine multiple validators on a single control? Let’s say our email field is
required and needs to match the shape of an email address.
FormControls takes a single synchronous and a single asynchronous validator, or, a collection of synchronous and asynchronous validators.
Here’s what it looks like if we’d combine the
required validator with our custom one:
Building custom validator directives
Now that we’re able to add our custom validator to our form controls imperatively when building model-driven forms, we might also enable our validator to be used in template driven forms. In other words: We need a directive. The validator should be usable like this:
validateEmail is applied as an attribute to the
<input> DOM element, which already gives us an idea what we need to do. We need to build a directive with a matching selector so it will be executed on all input controls where the directive is applied. Let’s start off with that.
We import the
@Directive decorator form
@angular/core and use it on a new
EmailValidator class. If you’re familiar with the
@Component decorator that this is probably not new to you. In fact,
@Directive is a superset of
@Component which is why most of the configuration properties are available.
Okay, technically we could already make this directive execute in our app, all we need to do is to add it to our module’s
Even though this works, there’s nothing our directive does at the moment. What we want to do is to make sure that our custom validator is executed when Angular compiles this directive. How do we get there?
Angular has an internal mechanism to execute validators on a form control. It maintains a multi provider for a dependency token called
NG_VALIDATORS. If you’ve read our article on multi providers in Angular, you know that Angular injects multiple values for a single token that is used for a multi provider. If you haven’t, we highly recommend checking it out as the rest of this article is based on it.
It turns out that all built-in validators are already added to the
NG_VALIDATORS token. So whenever Angular instantiates a form control and performs validation, it basically injects the dependency for the
NG_VALIDATORS token, which is a list of all validators, and executes them one by one on that form control.
Since multi providers can be extended by adding more multi providers to a token, we can consider
NG_VALIDATORS as a hook to add our own validators.
Let’s add our validator to the
NG_VALIDATORS via our new directive:
Again, if you’ve read our article on multi providers, this should look very familiar to you. We basically add a new value to the
NG_VALIDATORS token by taking advantage of multi providers. Angular will pick our validator up by injecting what it gets for
NG_VALIDATORS, and performs validation on a form control. Awesome, we can now use our validator for reactiveand for template-driven forms!
Custom Validators with dependencies
Sometimes, a custom validator has dependencies so we need a way to inject them. Let’s say our email validator needs an
EmailBlackList service, to check if the given control value is not only a valid email address but also not on our email black list (in an ideal world, we’d build a separate validator for checking against an email black list, but we use that as a motivation for now to have a dependency).
The not-so-nice way
One way to handle this is to create a factory function that returns our
validateEmail function, which then uses an instance of
EmailBlackList service. Here’s what such a factory function could look like:
This would allow us to register our custom validator via dependency injection like this:
We can’t use
useValue as provider recipe anymore, because we don’t want to return the factory function, but rather what the factory function returns. And since our factory function has a dependency itself, we need to have access to dependency tokens, which is why we use
deps. If this is entirely new to you, you might want to read our article on Dependency Injection in Angular before we move on.
Even though this would work, it’s quite a lot of work and also very verbose. We can do better here.
The better way
Wouldn’t it be nice if we could use constructor injection as we’re used to it in Angular? Yes, and guess what, Angular has us covered. It turns out that a validator can also be a class as long as it implements a
validate(c: FormControl) method. Why is that nice? Well, we can inject our dependency using constructor injection and don’t have to setup a provider factory as we did before.
Here’s what our
EmailValidator class would look like when we apply this pattern to it:
However, we now need to adjust the provider for
NG_VALIDATORS, because we want to use an instance of
EmailValidator to be used for validation, not the factory function. This seems easy to fix, because we know that we create instances of classes for dependency injection using the
However, we already added
EmailValidator to the
directives property of our component, which is a provider with the
useClass recipe. We want to make sure that we get the exact same instance of
EmailValidator on our form control, even though, we define a new provider for it. Luckily we have the
useExisting recipe for that.
useExisting defines an alias token for but returns the same instance as the original token:
Yikes! This won’t work . We’re referencing a token (
EmailValidator) which is undefined at the point we’re using it because the class definition itself happens later in the code. That’s where
forwardRef() comes into play.
If you don’t know what
forwardRef() does, you might want to read our article on Forward References in Angular.
Here’s the full code for our custom email validator:
You might notice that we’ve extended the selector, so that our validator not only works with
ngModel but also with
formControl directives. If you’re interested in more articles on forms in Angular, we’ve written a couple about template-driven forms and reactive forms.
Angular Master Class at Shopware
Join our upcoming public training!Get a ticket →
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Validators Pipeline in Angular 1.3
In this article we discuss a newly introduced feature called custom validators, so we don't have to hijack parsers and...
Two-way Data Binding in Angular
Two-way data binding was one of the main selling points of AngularJS. Since Angular, we can build directives that implement...
Custom Form Controls in Angular
Angular makes it very easy to create custom form controls. Read on to learn how to do it!
Reactive Forms in Angular
Angular allows us to build forms in a model-driven fashion. In this article we're going to discuss what that looks...
Template-driven Forms in Angular
In this article we discuss the template-driven forms in Angular and all the directives that come into play.
RxJS Master Class and courseware updates
If you've been following us for a while, you're quite aware that we're always striving to provide up-to-date and high-quality...