Why does adding additional AngularJS validation directives cause $asyncValidators
to run multiple times on page load?
I created a custom directive which
I followed @New Dev's advice and implemented a simple caching routine which fulfilled my requirement quite nicely, here's what I came up with ..
link: function (scope, element, attributes, ngModel) {
var cache = {};
ngModel.$asyncValidators.validateValue = function (modelValue) {
if (modelValue && cache[modelValue] !== true) {
return MyHttpService.validateValue(modelValue).then(function (resolved) {
cache[modelValue] = true; // cache
return resolved;
}, function(rejected) {
cache[modelValue] = false;
return $q.reject(rejected);
});
} else {
return $q.resolve("OK");
}
};
}
It actually took me a while to figure this one out. As mentioned in this post, Angular validators trigger additional validations. I decided not to fight this behavior and work around it instead, falling back to parsers and formatters:
myApp.directive('userSaved',['$q','dataservice',function($q, dataservice){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ctrl){
ctrl.$parsers.unshift(checkUserSaved);
ctrl.$formatters.unshift(checkUserSaved);
function checkUserSaved(value){
ctrl.$setValidity("usersaved") // the absence of the state parameter sets $pending to true for this validation
dataservice.getUserSaved(value).then(function(response){
var userIsSaved = (response === true);
ctrl.$setValidity("usersaved", userIsSaved); // the presence of the state parameter removes $pending for this validation
return userIsSaved ? value : undefined;
});
return value;
}
}
}
}]);
As a reference, you also might want to check the Angular docs
EDIT
Upon further investigation, it appears that in the case of ng-pattern the extra validations are only triggered when the regex is converted from a string.
Passing the regex directly:
<div ng-pattern="/^[0-9]$/" user-saved></div>
fixed the problem for me while making use of the validators pipeline.
For reference, see this github issue
This is because validation directives like ngMaxlength
, ngPattern
invoke an initial validation cycle with a call to ngModelController.$validate()
.
This causes all the validation directive to run their validation logic, including the async validators.
One way to prevent the redundant $http
calls, and in fact it is a good practice anyway, is to cache the validation result for each input.