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.

@ngserveio/validation-messages
Find more at libraries and examples at [NgServe.io](https://ngserve.io).

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?

  1. Create consistent validation messaging to the user
  2. Eliminate the bloat markup from having to handle multiple validation rules
  3. Don’t Repeat Yourself
  4. 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.

  1. control - The form control being validated
  2. fieldName - 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.

Free Your Developers

Nx Monorepo Starter alleviates developers from re-inventing deployments on popular Google Cloud Services, standardizes on libraries, and saves time for scaffolding projects.

View on Gumroad

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.