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:
This post was written for AngularJS 1.3 and may contain outdated information. Please see
this postfor 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 frameworkThe ngMessages module adds two directives that are designed to show and hide messages based on the state of the object it listens on.
Suppose that we have the following HTML.
<div ng-init="obj = {greet: true}">
<ng-messages for="obj">
<ng-message when="greet">Hello World!</ng-message>
</ng-messages>
</div>
This says, watch for obj
in the scope. If obj.greet
is true
, then show âHello World!â.
Now, remember that NgModelController sets an $error
object on itself whenever validation fails. This object contains key-value corresponding to validators (e.g. required, maxlength, etc.), and the value is true
whenever the model value is invalid.
For example, a sign-up form might have a username field with the follow $errors object.
$scope.signUpForm.username.$errors; // { required: true }
This composes very well with ng-messages
because the output of NgModelController ($error
) is the input of ng-messages (for="form.field.$error"
).
Letâs take a closer look at this form.
Error messages in formsSuppose we have the following sign-up form.
<form novalidate name="signUpForm" >
<label>
Username:
<input type="text" ng-model="user.username" name="username" required />
</label>
<ng-messages for="signUpForm.username.$error" ng-if="signUpForm.username.$dirty">
<ng-message when="required" class="error">This is required</ng-message>
</ng-messages>
<label>
Password:
<input type="password" ng-model="user.password" name="password" required />
</label>
<button>Sign Up</button>
</form>
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 pipelineAngular 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.
For our form, we can define a new directive uniqueUsername
that will be used as follows.
<label>
Username:
<input type="text" ng-model="user.username" name="username"
required
unique-username />
</label>
<ng-messages for="signUpForm.username.$error" ng-if="signUpForm.username.$dirty">
<ng-message when="unique" class="error">This is taken</ng-message>
<ng-message when="required" class="error">This is required</ng-message>
</ng-messages>
Here, we added unique-username to the input, and a new ng-message when âuniqueâ fails.
How do we implement this directive? Itâs actually very simple.
m.directive('uniqueUsername', function(isUsernameAvailable) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.unique = isUsernameAvailable;
}
};
});
Of course, I havenât implemented the isUsernameAvailable
service yet. But it can be simply done like so.
m.factory('isUsernameAvailable', function($q, $http) {
return function(username) {
var deferred = $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();
});
return deferred.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 changesThe new ngModelOptions directive allows us to configure the behaviour of the NgModelController. The one option we are interested in is debounce.
<input type="text" ng-model="user.username" name="username"
ng-model-options="{ debounce: 100 }"
required
unique-username />
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 readingsIn 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.
For learn more about these topics, here are some useful links.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4