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.