anoonimo
anoonimo

Reputation: 367

Avoid setTimeout to overlap the function on angular $watch

I have this validation process with two divs fading in or out depending on the return of the first function. The setTimeout is to let finish type the input since it watches on keyup. The system works but the problem happens when:

you type a wrong value (#wrong shows up) > you re-type the good value > while you're typing the good value the else function is triggered because it's not complete yet > when the good value is typed (.ok shows up) the #wrong shows up as well with after delay of the setTimeout.

Is there a way to avoid the overlapping of the else function? Maybe separating completely the true and false functions?

function validatePasscode(code) {
    if (array.indexOf(code) > -1) {
        return true;
    }  
}

$scope.$watch('passcode', function(value) {
    var isValid = validatePasscode(value);
    if (isValid) { 
        $scope.loadNewChannel(); 
        $('.ok').fadeIn(200); 
        $('#wrong').fadeOut(200);
    }
    else {
        setTimeout(function(){
            $('.ok').fadeOut(200); 
            $('#wrong').delay(200).fadeIn(200);
        },2000);
    }
});

Upvotes: 0

Views: 1133

Answers (2)

Jeremy Likness
Jeremy Likness

Reputation: 7531

I would take a bit of a different approach. The idea of Angular is not to do DOM manipulation inside of your controllers. You have a few options - one would be to expose $isValid on the $scope and another would be to create a directive that does the animations.

As for throttling the input, the problem is you arbitrarily wait 2 seconds after every key press. What you really want is to wait until the key presses settle - i.e. as long as someone is typing wait, then once they've paused for a period re-evaluate.

You can do that with Underscore or Lo-dash's debounce. Something like this:

var applyValidation = function($scope) {
   $scope.isValid = validatePasscode($scope.passcode);
}

Because debounce fires outside of Angular's digest loop, you need to $apply it:

var validationDelayed = function($scope) {
    $scope.$apply(function(){applyValidation($scope);});
};

Then you can watch it and only react once the input settles. This will only fire the check after the user has stopped typing for 1 second:

var validationThrottled = _.debounce(validationDelayed, 1000);
$scope.$watch('passcode', function(){validationThrottled($scope);});

Again, this will solve the time out but I strongly recommend moving this into a directive for the animations, although you can certainly try it out in this code like this:

var applyValidation = function($scope) {
   if(validatePasscode($scope.passcode)) {
      // success animation
   } 
   else {
      // failure animation
   }
}

Here's a working fiddle of mine that uses a similar concept: http://jsfiddle.net/jeremylikness/5KeMw/ (I'm filtering instead of validating but you can see how the throttle works) and a blog post about it here: http://csharperimage.jeremylikness.com/2013/10/throttling-input-in-angularjs.html

Upvotes: 1

Paulo Lima
Paulo Lima

Reputation: 1238

please always call the clearTimeout ()

var timeoutFunction;
$scope.$watch('passcode', function(value) {
var isValid = validatePasscode(value);
clearTimeout(timeoutFunction);
if (isValid) { 
    $scope.loadNewChannel(); 
    $('.ok').fadeIn(200); 
    $('#wrong').fadeOut(200);
}
else {
    setTimeout(function(){
        $('.ok').fadeOut(200); 
        $('#wrong').delay(200).fadeIn(200);
    },2000);
}

});

Upvotes: 0

Related Questions