Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
376 views
in Technique[技术] by (71.8m points)

javascript - Angular expressions in $watch trigger twice

Why does this $watch fire twice when a simple comparison is passed as the watchExpression?

$scope.foo = 0;  // simple counter

$scope.$watch('foo > 4', function() {
  console.log("foo is greater than 4: ", $scope.foo);
});

The listener fires when the page loads, when foo is 0, then once more (and only once more) when the value of foo goes above 4.

Why does the listener fire when the page loads? And why doesn't it continue to fire when foo is greater than 4?

I set up a simple plunkr to show what's happening: http://plnkr.co/edit/ghYRl9?p=preview

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

After re-reading the Angular $watch docs a few more times, I think I understand what's happening.

The listener is called only when the value from the current watchExpression and the previous call to watchExpression are not equal

Angular tracks the value of the watchExpression foo > 4 with each digest() loop. Because this evaluated to false until foo was greater than 4, the listener didn't fire. Likewise, after foo was greater than 4, the values Angular was comparing were both true. The only time it detected a change was when the evaluated expression crossed over.

Two values are passed to the $watch listener function, the new value and the old one. Logging these values shows that the watchExpression is being evaluated, and Angular is looking for a change in those values.

$scope.$watch('foo > 4', function(newVal, oldVal) {
  console.log("foo is greater than 4: ", $scope.foo, oldVal, newVal);
});

// foo is greater than 4:  5 true false

The reason the listener was called on page load is also documented:

After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.

I updated the plunkr with a second $watch that evaluates the value of foo internally:

$scope.$watch('foo', function(newVal, oldVal) {
  if ($scope.foo > 4) {
  console.log("foo is greater than 4: ", $scope.foo, newVal, oldVal);
  }
});

// foo is greater than 4:  5 5 4
// foo is greater than 4:  6 6 5
// foo is greater than 4:  7 7 6

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...