11

Take a look at example on http://jsfiddle.net/2NJ7y/3/ (version of AngularJS 1.0.1). There is simple app, which is waiting for entering of lucky number. If the number is equal to 7, I reset lucky number to null. If I enter number 7 several times, sometime/randomly the lucky number stay in input field. Why? How this behavior solve? Thanks.

user1595465
  • 1,161
  • 4
  • 11
  • 16
  • This is a good question. You are getting into a kind of race condition here which is the reason why it is not getting resolved properly. Would be interested in finding out a proper way of resolving this. – ganaraj Aug 29 '12 at 12:44
  • 1
    What about using [$timeout service](http://docs.angularjs.org/api/ng.$timeout)? http://jsfiddle.net/2NJ7y/10/ But I've never understood why it works with 0 delay. – Artem Andreev Aug 29 '12 at 13:58
  • @Artem: It isn't so nice workaround, but works. But I'm still waiting for ultimate solution. :-) Thanks. – user1595465 Aug 29 '12 at 14:04
  • @Artem, a comment by Misko indicates that $timeout (used to be called $defer) is run after the browser renders: https://github.com/angular/angular.js/issues/734#issuecomment-3657272 I suppose this helps avoid the race condition between the controller function trying to change the value, and the browser rendering the '7'... maybe?? – Mark Rajcok Aug 29 '12 at 16:01
  • @Mark Thanks. I also found [good explanations about setTimeout(fn, 0)](http://stackoverflow.com/questions/779379/why-does-settimeoutfn-0-sometimes-help). – Artem Andreev Aug 29 '12 at 16:04

3 Answers3

6

I've done some debugging.

Firstly for me lucky number stay in input field not randomly.

enter 3 (model==3, input==3) => enter 7 (alert, model==null, input="")

=> enter 3 (model==3, input==3) => remove 3 (model=="", input=="")

=> enter 7 (alert, model==null, input="")

=> enter 7 (alert, model==null, input="7")

7 stay in input field only if previous model value was null.

What happens: when you input 7 fired input event which is handled by listener function of input directive. Listener function calls $setViewValue. $setViewValue sets $viewValue, $modelValue, model value and calls $viewChangeListeners (ngChangeDirective simply adds handler to $viewChangeListeners). Alert is displayed, luckynumber is set to null. After all that if luckynumber differs from previous value on previous dirty checking $watch handler and $render are called.

In my examples $render called if previous model value was "3" or "". If previous model value was null $render isn't called.

Why $timeout with 0 delay works: when you call $timeout with 0 delay function with changing luckynumber to null is postponed at the end of events queue (all javascript in a browser executes on a single thread). $viewChangeListener is not change model value from 7 to null. $digest finishes. Then $timeout handler is called. Model value is set to null. $watch handler and $render are called. $render sets input value to "".

Artem Andreev
  • 19,942
  • 5
  • 43
  • 42
3

At long last, a solution. Use $watch instead of ng-change:

$scope.$watch('luckynumber', function() {
    if ($scope.luckynumber == 7) {
        alert('The lucky number mustn\'t be equal 7.');
        $scope.luckynumber = null;
    }
})

Fiddle.

This other SO answer by @Valentyn made me think of trying that solution to this question.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
1

If you simply put

$scope.luckynumber = undefined;

prior to the alert you don't eliminate the race condition but you do change it so that 7 does get cleared properly, but sometimes you get the alert twice.

If the alert code is replaced by something idempotent, like altering the DOM to display an error, then this issue won't be important.

psr
  • 2,870
  • 18
  • 22