Asynchronous form errors and messages in AngularJS

Angular 1.3.0 (superluminal-fudge) has been released!
In a previous post, I presented a framework on top
of AngularJS 1.2 to handle asynchronous form validations and error message display concerns. This has been made
much easier in AngularJS 1.3!

In this post we will cover three new features:

ngMessages

$asyncValidators on NgModelController

ngModelOptions

Warning: This post was written for AngularJS 1.3 and may contain outdated information.
Please see this post
for things to watch out for when reading older Angular posts. If you would like me to update the content of this post,
please ping me on Twitter at @jay_soo. If there are enough interest, I might
make an update.

New messaging framework

The ngMessages module adds two directives that are designed to show and hide messages based on the state of the object
it listens on.

Here, we have the signUpForm with a username field. The username field has a required validator.
When the required validator fails, signUpForm.username.$error.required is set to true.

And since ng-messages is watching the signUpForm.username.$error object, whenever the required validator
fails, we will see the message “This is required”.

This is fine and all. But what happens when we want to perform a uniqueness check on the username? This operation
must be done asyncly because only the server would know if a username exists or not.

In comes $asyncValidators to the rescue!

New validation pipeline

Angular 1.3 adds two new properties to NgModelController: $validators and $asyncValidators.

Both properties define a pipeline for validation to be run against a given value. This replaces
the previous validation pipeline through ngModel.$parsers and ngModel.$formatters.

For $validators, the functions return true if a value is valid, and false otherwise. And for
$asyncValidators, the functions return a promise that is resolved if a value is valid, and rejected
otherwise.

Async form validations

For our form, we can define a new directive uniqueUsername that will be used as follows.

Of course, I haven’t implemented the isUsernameAvailable service yet. But it can be simply done like so.

m.factory('isUsernameAvailable',function($q,$http){returnfunction(username){vardeferred=$q.defer();$http.get('/api/users/'+username).then(function(){// Found the user, therefore not unique.deferred.reject();},function(){// User not found, therefore unique!deferred.resolve();});returndeferred.promise;}});

Nice! This is exactly what we needed. Both error messages and async validations are working now!

One tiny issue remains. Our new validator is called everytime the user types something. If the user
types five characters within a second, we just wasted five HTTP requests when one would have sufficed – the one
being the final word.

The solution for this performance issue is our last topic.

Debouncing model value changes

The new ngModelOptions directive allows us to configure the behaviour of the NgModelController. The one option
we are interested in is debounce.

Here, we’re defining a debounce of 100 milliseconds. This means that any parsing and validation will be delayed until
after 100 milliseconds have elapsed since the last time they’ve been invoked. Basically, if we type 2 characters every
100 milliseconds, we’d expect up three HTTP requests for the five-lettered word (instead of 5).

This new feature helps improve performance whenever an expensive operation is being called multiple times.

Wrap-up and further readings

In this post, we covered how to hook form validations into the ngMessages framework. We went over async validations on
model value. And lastly, we looked at the debounce option of ngModel to optimize the number of expensive operations.