Ben Cook
Ben Cook

Reputation: 1684

How to cancel/ignore AngularJS asynchronous validation check before resolution?

I have a simple HTML5 user registration form. The value of the username input is validated to ensure that there are no users with the same username using a custom function added to $asyncValidators.

Consider the following situation:

  1. User starts typing in a username (e.g., 'Me').
  2. After a slight delay, Angular validators run, sending asynchronous request to server asking if the username is available.
  3. User begins typing more characters (e.g., 'Me123').
  4. After another slight delay, Angular again tries to validate the username by sending another request to server.

What does Angular do when it receives two responses regarding the validity of the username? Is there a way to handle this gracefully?

What I'd like to do is somehow cancel or tell Angular to ignore the result of the first validation attempt as soon as the user modifies the username value.


EDIT 11/12/2015:

sirrocco's answer helped me clarify my question. There are two pieces here:

  1. abort actual HTTP request
  2. tell Angular validation process to stop waiting for the promise returned from $asyncValidators.usernameAvailable to be resolved or rejected

I could do the first by using the timeout option of the $http service. My question is: Is it necessary, desirable, or possible to do the second? If so, how?

I'm thinking this might happen in a a keyup event handler added to the element in the directive link. See below:

index.html

<input 
  ng-model="user.username" 
  ng-model-options="{ debounce: 250 }" 
  check-availability />

checkAvailability.directive.js

function affectsText(keyCode) {
  // some code to determine if keyCode could change value of a text input
}

angular
  .module('app')
  .directive('checkAvailability', ['$q', 'users', function($q, users) {
    return {
      restrict: 'AC',
      require: 'ngModel',
      link: function (scope, element, attrs, ctrl) {
        element.on('keyup', function(e) {
          if (!affectsText(e.keyCode)) {
            return;
          }

          // cancel previous $asyncValidators.usernameAvailable process??

          // users service cancels operation by rejecting a promise previously assigned to to $http's timeout option
          users.cancelIsAvailableCheck();
        });

        ctrl.$asyncValidators.usernameAvailable = function(modelValue) {
          if (ctrl.$isEmpty(modelValue)) {
            return $q.when();
          }

          var deferred = $q.defer();

          users.isAvailable(modelValue).then(function(available) {
            if (available) {
              deferred.resolve();
            } else {
              deferred.reject();
            }
          });

          return deferred.promise;
        }
      }
    };
  }]);

Upvotes: 2

Views: 1383

Answers (1)

sirrocco
sirrocco

Reputation: 8055

The simplest way would be to just disable the user input like:

<input 
  name="username"
  ng-model="user.username" 
  ng-model-options="{ updateOn: 'blur' }"
  ng-disabled="myForm.$pending"
  check-availability />

So once the user tabs out of the username field, the validation is triggered and the field is disabled (you could also disable to submit button).

If that's not an acceptable UX, you could instead use the method described here : http://www.bennadel.com/blog/2616-aborting-ajax-requests-using-http-and-angularjs.htm

Essentially $http allows for a promise to be set on its timeout property. If you then resolve that property, the http call is aborted. It might take some trial and error to figure out the best approach within the validator.

Upvotes: 1

Related Questions