A common complaint when it comes to creating custom validators with Angular template-driven forms is the boilerplate that one has to write before the validator can be used.
While I partly agree with that remark, it also has a clear separation of concerns because it adds a layer to validate the model. For the complex models, I take the opportunity to use the validator in a way that the validator is acting as a separate layer, containing the business rules.
The part where I do agree is that you have to touch (and review) multiple files to create and register the directive. The extra overhead (even if it's only a small effort) to add the validator directive to an Angular module is also often forgotten, which leads to frustrations.
But as we'll see, this doesn't have to be the case. We can create one validator to rule them all.
Let's simply call this validator
ValidatorDirective, and all this directive does is accept a callback to a method that returns the validation errors.
ValidatorDirective we can now validate a form while defining inline validators.
For example, to validate a single
ngModel, we create the validator in the component.
The validator (
scoreValidator) receives the control and returns the validation errors, just like the
validate method from the
To use the inline validator in the template, you assign the
validator attribute (this is the selector from the
ValidatorDirective directive) to the validate method (
Instead of having to write all the validators manually, you can also invoke the built-in Angular validators, or invoke your custom validators.
This is quick and easy for simple one-off validators.
Because validating a form group or even an entire form might get complex, it's a best practice to extract the validation rules outside of the component into its own method or class. This keeps the component small and simple, and makes the validation logic easier to test.
To validate a
ngModelGroup you can reuse the same validator directive.
Note that I'm using the arrow syntax while declaring these validators. I do this to scope the method to the component class, instead of the directive class. This allows me to use other class properties within the validator method.
To make the validator react to another form value, we can use the
registerOnValidatorChange method to revalidate the control. Each time a new value is set (via the
value input), the control is revalidated.
You can read more about this technique in my Template-Driven Forms guide.
To use the revalidation functionality, you assign the
value of the validator to a value that affects the validator.
In the example below, the value of the password is passed to the password confirmation validator.
We can eliminate some of the "boilerplate" 🙊 by creating one generic validator directive that accepts a callback to validate a form model. This allows us to create inline validators within components. While this can be quick and easy for simple validations, I prefer to extract the complex validators into their own layer.
When the validation logic lives on its own (and not in a directive or in the component), it also doesn't bind the business rules to an Angular-specific layer.
Please consider supporting me if have you enjoyed this post and found it useful: