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
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 ofwatchExpression
didn't change. To detect this scenario within thelistener
fn, you can compare thenewVal
andoldVal
. 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With