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 themyForm.name
will becomefalse
.$invalid
property of themyForm.name
becomestrue
.myForm.name.$error
object contains{ required: true }
$valid
property of themyForm
(the form enclosing the input box) becomesfalse
.$invalid
property of themyForm
becomestrue
.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.