Angular Tutorial - Part 1: Reusable Validation Messages
Sanitizing user input left my form markup bloated. Common validation messages repeated. Messages varying by field name felt redundant. How many different variations did I want to create saying the form requires a field?
Below shows bloated markup in an Angular Material form field with the validations for an email input. Many examples I found in documentation follow similar displays for validation messages.
Above exemplifies multiple conditions for email address validation. The form checks conditions of required, emailFormat, and emailAddressTaken.
I noticed the pattern between each error message, far from Don’t Repeat Yourself.
{{fieldName}} is required.
{{fieldName}} is in an invalid format.
{{fieldName}} {{value}} is already used.
The errors property on the form control provides a map of validation errors. So on the template, an *ngIf directive chooses if the error displays the existence of validation error, ie: required.
So how do I create validation messages once and use it everywhere?
What's the Goal?
- Create consistent validation messaging to the user
- Eliminate the bloat markup from having to handle multiple validation rules
- Don’t Repeat Yourself
- Again, DON’T REPEAT YOURSELF… shit
FormControl errors Property
The FormControl
errors
property provides a ValidationErrors
class which is a map of validation violation key of type any
. As noted in HTML above, the validation errors consist of required
, emailFormat
, and emailAddressTaken
. With these known keys, this affords the ability to create our own validation messages map based on the FormControl
error’s keys.
Implementation
I created a constant VALIDATION_MESSAGES
map of the validation errors for our email address. Note, the required
validation message could be reused for multiple form controls that implement the Validators.required
validator. I expect this list of validation messages to grow as forms differ in their requirements.
Next, I’m going to create another const for an injection key for consuming apps should it need to extend the default error messages for custom validations or overwrite the default error messages.
Now, I generate a directive that handles the display of messages.
In directive’s constructor, I inject the element for the error messages to bind to the inner text of the element, and the error messages.
The ValidationDisplayDirective requires two inputs.
control
- The form control being validatedfieldName
- The field name of the control being validated
a) This provides a label to the{{fieldName}} is required
messages.
On initialization of the directive, I subscribe to the valueChanges
of the control. I then confirm if the control is dirty
or touched
, and invalid
. From here, I retrieve the error message based on the errors
of the form control, and if any of these error keys also belong to messages
. The formatString
provides a utility method to interpolate the message string and replace with the key values of the errorData
.
If the error data given to format string consisted of:
And for instance the validation message string was: {{fieldName}} must be greater than {{min}}. Received value was {{value}}
. The output would render:
Rating must be greater than 5. Received value was 0.
I add the ValidationDisplayDirective
to my module and export it to be consumed in other modules.
Importing the Module
Above shows an example of a consuming module of the ValidationModule
. I extended the error messages to include a message for requiredIf
. This assumes I have a validator that returns ValidationErrors
with a requiredIf
key. Below is an example of that validator.
The requiredIf
validator returns an object containing the independentFieldName
. For example, if independentFieldName
is ‘Test Field 1’. The message displayed to the user would render:
Email is required when Test Field is selected.
Implementing the ValidationDisplayDirective
I can strip the markup of many specific validation errors using the directive, making this more DRY. I’ll add a couple of other inputs for example.
At this point, I’ve stripped the markup’s responsibility of displaying error messages. Those form fields though, this still feels bloated.
Shit... The form repeats itself with the variations being the fieldName
property and the miValidationDisplay
directive.
In Part 2, I'll create a Angular Material form field that encompasses the validation messages and field names.