Here's my two cents. These are the changes that I would make:
- Separate the API calls into a service.
- Separate the event triggers using the model-options in the view.
- Handle the monitoring using a watch. (Expensive, but it seems to be the "angular" way.)
- Isolate your code into functions/files, then configure in
application.js
.
Helper Functions
Normally, I use !!value
to test if a value is not undefined
or null
, but in this case I want to do things properly and make the code readable. Alternatively, if you use library like lodash or underscore, you can just call !_.isEmpty(value)
function notEmpty(value) {
return (angular.isDefined(value) && !angular.isNull(value))
}
View
Using ngModelOptions will allow one to fine tune the view without having to manipulate the directive. One can then test different events without having to make changes in the directive.
<input type="text"
name="username"
ng-model="form.username"
ng-model-options="{ updateOn: 'blur' }"
unique-username=""
required
/>
<span class="hide-while-in-focus" ng-show="thisform.username.$error.unique">Username taken!</span>
Service
Using a service is another large advantage. Currently, the directive requires $http
and thus becomes bound to the api. Using a service, one can create a mock service to test the directive independently of the api.
Another advantage is that the logic for the uniqueness test is now isolated from the view. This code can then be reused in a controller, another directive, or even another service.
function UserService($q, $http) {
this.isUnique = function(username) {
if (notEmpty(username)) {
var uri = '/api/checkUnique/' + username;
return $http.get(uri);
}
return $q.reject("Invalid User name");
}
}
Directive
userService
is must now be injected into the directive instead of $http
. Since the uniqueness test is done when the model changes, one can also create a watch on the model rather than binding it to an event.
In the future, you can use the controller.$parsers.unshift
command instead of a scope.$watch
. Unfortunately, $parsers
doesn't allow for responses from a $promise
. See issue #6416.
function UniqueUsernameDirective(userService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function(value) {
userService.isUnique(value)
.then(function(data) {
ngModel.$setValidity('unique', notEmpty(data));
})
.catch(function() {
ngModel.$setValidity('unique', false);
});
});
}
};
}
Application.js
This just keeps your application.js
file nice an clean.
angular.module('app', [])
.service('userService', ['$q', '$http', UserService])
.directive('uniqueUsername', ['userService', UniqueUsernameDirective]);
Demo
I made a demo using a mock service to demonstrate this functionality. jsFiddle
thisform...
, too easy to confuse withthis.form...
\$\endgroup\$ – Dmitri Zaitsev Aug 3 '14 at 5:10$asyncValidators
- odetocode.com/blogs/scott/archive/2014/10/16/… \$\endgroup\$ – Adam Zerner Mar 9 '15 at 15:00