Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

Why does adding additional AngularJS validation directives cause $asyncValidators to run multiple times on page load?

I created a custom directive which implements $asyncValidators. This is the basic structure of that custom directive:

myApp.directive('userSaved',['$q','userLookup',function($q, userLookup){
return {
  restrict: 'A',
  require: 'ngModel',
  link: function(scope, elem, attrs, ctrl){
    ctrl.$asyncValidators.userSaved = function(modelValue, viewValue) {
      // do stuff 
    }
  }
}
}]);

The controller initializes the tailNumber model value like this:

$scope.tailNumber = 'N33221';

This is the html where the user-saved directive runs 3 times on page load:

<input ng-model="tailNumber" name="tailNumber"   user-saved 
    ng-minlength="2"   ng-pattern="/^[A-z][a-zA-Z0-9]*$/" >

When I remove ng-minlength="2" then the user-saved directive runs twice on page load (2 times). This is the html with ng-minlength="2" removed:

<input ng-model="tailNumber" name="tailNumber"   user-saved 
    ng-pattern="/^[A-z][a-zA-Z0-9]*$/" >

When I remove ng-pattern="/^[A-z][a-zA-Z0-9]*$/" then the user-saved directive only runs 1 time. This is the html after removing ng-pattern="/^[A-z][a-zA-Z0-9]*$/"

<input ng-model="tailNumber" name="tailNumber" user-saved >

Why does my function registered with $asyncValidators run an additional time for each additional ng validator attached to the form element?

My custom directive is an expensive $http call, and I prefer my custom directive only run once on page load. Is it possible to use all of these ng validators and while only running my async validator function one time instead of 3 times on page load?

share|improve this question
up vote 1 down vote accepted

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.

share|improve this answer
    
The documentation says ngModelController will run first synchronous validators and then asynchronous validators. So why is ngModelController running my asynchronous directive while running the synchronous ngMinlength and ngPattern? – steampowered Jul 31 '15 at 14:59
    
@steampowered, right - first sync, and if they succeed, then async... The sync validators clearly succeed (which they do for empty values, for example), then async validators run – New Dev Jul 31 '15 at 15:33
    
@steampowered, did this address your question? – New Dev Aug 10 '15 at 8:45
    
The answer was helpful but I think there might be more going on with this issue. The ngMinlength and ngPattern should run synchronously before the ansyn validator runs, which is not happening. I ended up removing the ngMinlength and ngPattern validators because they didn't play nicely with an $asyncvalidators implementation (I removed all the code in the directive, but still my bare directive caused problems with the ngMinlength and ngPattern validators). So I have working code, but I think there may be more going on here than this answer addresses. – steampowered Aug 10 '15 at 15:33
    
@steampowered, what makes you say that ngMinlength and ngPattern do not run synchronously? They do. If you are uncertain, put breakpoints in their src code (e.g. here). – New Dev Aug 10 '15 at 21:59

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

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.