Reputation: 3670
I have the following example:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.testMethod = function() {
alert('hi');
}
});
<!DOCTYPE html>
<html>
<head>
<script data-require="[email protected]" data-semver="1.5.0" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
{{testMethod()}}
</div>
</body>
</html>
I have called the method only once in view template. But why is it executed twice?
Upvotes: 10
Views: 941
Reputation: 851
When you use functions in binding expressions Angular will reevaluate the expression on every $digest phase. The reason behind this is that functions can return a response but there is no way for Angular to know if the result won't change in the next function call.
Angular ensures that it will show the latest correct up to date value in the only way possible in this case - by calling the function every time there is a change in the scope.
You'll see people calling this "$digest phase". It can occur due to many reasons. For the sake of this explanation I'm simplifying stuff. If you want to know more read https://docs.angularjs.org/guide/scope
As a general rule - avoid binding to a function. Instead remember the function response in a $scope
variable and bind to it instead. Binding many times to a function might lead to performance issues when bindings count grows in your project.
Let's change the post example. Open the console and run the code below.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var i = 0;
$scope.testMethod = function() {
alert('hi');
i++;
return i; // Return a different value every time
}
});
<!DOCTYPE html>
<html>
<head>
<script data-require="[email protected]" data-semver="1.5.0" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
{{testMethod()}}
</div>
</body>
</html>
For the example purposes it's important for $scope.testMethod
to return different value every time it's called.
You'll notice the following:
After you see it multiple times the following error will appear:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
So, what happened? Every time we change the $scope
state here angular runs another digest phase until it "stabilizes" (there are no more changes). There is a limit on 10 consecutive $digest phases.
In the thread post angular calls $digest when the controller is attached and then, because we change the $scope, it calls one more digest. If we remove all the bindings from the code only one digest will occur.
We can easily check this. Remove the {{testMethod()}}
line from the example and then place a breakpoints in angular code: $digest, line 16700 (angular 1.5.0).
On this line you'll see if ((dirty || asyncQueue.length) && !(ttl--)) {
.
Your breakpoint will now be hit only once. Let's look 2 lines above. Angular team wrote an interesting comment there:
// `break traverseScopesLoop;` takes us to here
It's a self explanatory comment and if we go up to line 16629 we'll see
do { // "while dirty" loop
There is a variable in the code named "dirty" that creates a do...while
loop. While the scope is dirty (there are changes detected) you'll stay in this loop. A change is any change on any scope variable used in any way by any kind of binding ($watch).
That's why we have 2 $digest phases here.
You can find Angular 1.5 here. For debugging purposes I've switched to the non-minified versions.
Upvotes: 12
Reputation: 4066
avladov's answer is correct, but perhaps doesn't cover what sahbeewah asked. The update on the answer is explanatory, but honestly the answer boils down to much simpler point.
Angular pairs all watchers with special property (called last
in v1.4.10).
The initial value of last
is function initWatchVal() which is a private value in angular and thus you cannot return without some serious hacks.
Dirty checking works by evaluating the expression and comparing to previous evaluation (the value of last
).
If these values don't match, new round of evaluations is scheduled.
The point being: as you can't return the initial value, new round of evaluations is always scheduled after the first evaluation of new watchers.
Upvotes: 1