Form Validations and Other Cool Stuff with Angular ngModel $parsers and $formatters

Form Validations and Other Cool Stuff with Angular ngModel $parsers and $formatters

Developing forms can sometimes be trickier when different kinds of form validations are involved. When using angular, this becomes even difficult when we use both the user input as well as an angular model (ng-model) to update the content of a form element, for example, an input.

When the content of the input changes from both the user input and programmatically from ng-model, we can use angular’s ngModel $parsers and $formatters to evaluate update input value and validate.

$parsers

$parsers are an array of functions which will be executed when the $viewValue of a model is changed. $viewValue of a model is changed only when the user changes its value, such as typing, programmatic filling using jquery etc. We need to run our validators in a parser in order to validate the fields when the user changes an input.

$formatters

$formatters are an array of functions similar to $parsers which will run as a pipeline, but only when the $modelValue of the model is changed. $modelValue of a model is only changed when the model is updated programmatically by an angular controller. To validate the input when its value is programmatically changed by angular, we need to add a formatter function, inside which we run the validators.

We are now going to write a custom directive which can be used for validation. See the following example.

We have imported ngModel controller into the directive using require: 'ngModel' property. You’ll see later in this post that, after importing ngModel directive, we can access the $parsers, $formatters and some other model properties inside the directive.

In this example, we enforce that the name should only contain alphabetic characters and spaces. For validations, we use validateName function, which returns an array of validation errors if any.

Then we have created a function named setValidity, which will accept an array of validation errors, and for each validation error, it will call the model’s built-in $setValidity method. $setValidity method is a built-in method in ngModel which can toggle the validity status of the input and it accepts two parameters.

$setValidity([string] validationErrorKey, [boolean] isValid)

We will see what happens if we provide the following validation errors array to our local setValidity method.

setValidity(['min_length','invalid_chars'])

Our setValidity method will go through this array and will call model’s built-in $setValidity method for each string element in the array with the second argument as false.

function setValidity(errors) {    
    ...                         
    errors.forEach((val) => model.$setValidity(val, false));                   
}

Now we are writing an HTML form that uses this newly created directive.

Note that I have used some bootstrap styles here.

Let’s see what happens when model.$setValidity method is called with first parameter as required and the second parameter as false.

  • $valid property of the myForm.name will become false.
  • $invalid property of the myForm.name becomes true.
  • myForm.name.$error object contains { required: true }
  • $valid property of the myForm (the form enclosing the input box) becomes false.
  • $invalid property of the myForm becomes true.
  • myForm.$error object will contain the followings:
"$error": {
        "required": [
            {
                "$validators": {},
                "$asyncValidators": {},
                "$parsers": [
                    null
                ],
                "$formatters": [
                    null,
                    null
                ],
                "$viewChangeListeners": [],
                "$untouched": true,
                "$touched": false,
                "$pristine": true,
                "$dirty": false,
                "$valid": false,
                "$invalid": true,
                "$error": {
                    "required": true
                },
                "$name": "name",
                "$options": null
            }
        ]
    }

Now let’s go back to our directive.

In our local setValidity function, we are clearing existing errors of the model in the first line. Take a look at the following line.

Object.keys(model.$error)
    .forEach((k) => model.$setValidity(k, true));

We iterate through the keys of model.$error object, and we remove those validation errors using model.$setValidity function. The intention of this line is to clear any existing errors before adding new validation errors into the $error object.

Now, we are going to look at how our $parsers and $formatters are going to help us do the validations when the input changes.

We are now pushing a new function (a parser) at the start of the $parsers array. This function will run when the $viewValue of the model changes, with the first parameter as the updated $viewValue. This method returns a value that will be reflected in the UI input.

In the first line of our new $parser method, we run the validateName method which will return an array of validation errors, and pass it to our local setValidity method, which will update the validation status of each validation error. If there are no errors, setValidity method will clear all existing errors and exit without doing anything. Now, this new parser will help us run the validation checks when the user changes the input.

Let’s see how to run validations when the model changes programmatically.

$formatter function is identical to the $parser function, when the model changes, this function will be invoked with the first parameter as the updated $modelValue. Similar to the previous case, we’ll run the validators as the first step of this $formatter method as well.

Now, we are ready to display these validation errors in UI. We’ll display a red border around the text input, if the input is invalid. For this, I use bootstrap has-error class along with ng-class directive.

We can’t stop there. We need to show the user what the validation errors are, so that he can correct them.

We’ll add a bootstrap alert box to display any validation errors. We currently have three types of validation errors, required, min_length and invalid_chars. For these three, we’ll display three unique messages.

Now we can see the validation errors as follows:

See the complete example here in Plunker!

We have now completed a basic form validation with a single input box. We can scale this to multiple inputs easily. When there are multiple inputs, form will only be valid if all of its inputs are valid. If there are any validation errors in any of the form’s fields, $error object of the form’s model will list them. In a future article, I’ll discuss more cool things to do with angular form validations.