5

I've got an angular app like:

angular.module('ngStyleApp', [])

.controller('testCtrl', function($scope) {
   $scope.list = [1,2,3];
   $scope.getStyles = function(index) {
       console.log('getting styles for index ' + index);
       return {
           color: 'red'
       };
   };
});

with the corresponding markup:

<div ng-app="ngStyleApp">
    <ul ng-controller="testCtrl">
        <li ng-repeat="value in list" ng-style="getStyles($index)">
            {{value}}
        </li>
    </ul>
</div>

The visible output is three red list items, as expected. But the statement is logged to the console a total of 6 times, implying that the view is rendered twice:

getting styles for index 0
getting styles for index 1
getting styles for index 2
getting styles for index 0
getting styles for index 1
getting styles for index 2

Why?

calebds
  • 25,670
  • 9
  • 46
  • 74

2 Answers2

6

The Angular $digest cycle evaluates the ngStyle attribute at least twice - once to get the value and once to check if it has changed. It actually keeps iterating until the value settles so could potentially check the value many times.

Here's a picture to illustrate this:

enter image description here

Here is a good blog post illustrating this: angular digest blog

Actually, try this quote from StackOverflow that says it very well:

When watched functions are evaluated (during $digest) if any of them have changed from the previous $digest then Angular knows that change might ripple through to other watched functions (perhaps the changed variable is used in another watched function). So every watch is re-evaluated (also called dirty processing) until none of the watches results in a change. Thus typically you'll see 2 calls to watched functions per digest and sometimes more (up to 10- at 10 loops through it gives up and reports an error saying it can't stabilize).

(Reference here)

Community
  • 1
  • 1
Caspar Harmer
  • 8,097
  • 2
  • 42
  • 39
  • Then, why is that ng-init is only called once? – Josep Sep 19 '14 at 04:07
  • 1
    ng-init does just that - it is used to set up a value once to initialize the directive – Caspar Harmer Sep 19 '14 at 04:09
  • It is because ng-init does not need to reevaluate, it does not register any watches..No dirty checks basically needed. – PSL Sep 19 '14 at 04:10
  • @PSL the reason why ngInit doesn't get executed again is because ngInit doesn't have a watch, while ngStyle does. What I mean with this is that it's the watch of the directive what makes the function to be called twice, not the cycle itself (the cycle makes it possible, of course, but it's not the real reason). – Josep Sep 19 '14 at 04:18
  • @Josep I think you make a good point - the watch is definitely why the function gets called twice. – Caspar Harmer Sep 19 '14 at 04:20
  • @Josep yes that is what i mentioned in my comment (unless my wording was wrong :)), watches are registered for dirty checks during the digest cycle and to decide whether there is an update or not, in case of ng-init it does update during the first digest cycle, it is only for initialization there is no need to reevaluate it – PSL Sep 19 '14 at 04:21
  • @PSL I wasn't trying to rectify what you said, I just wanted to make clear what my point was with my first comment ;) Thanks! – Josep Sep 19 '14 at 04:24
  • @CaspNZ thanks; this makes sense. In my real-life case, the function providing styles is using a non-trivial algorithm to determine the absolute positioning of its respective element, and I'm seeing it called up to seven times per digest loop (I'm guessing due to my nested controller/directive setup). This understanding makes me want to pre-compute those values and store them in the controller, so the ng-style function just reads them. – calebds Sep 19 '14 at 04:27
  • @Josep oh no i dint mean that, sometimes my English isn't that good :(, so was just making sure by explaining again what i meant.. lol. Anytime happy to get my understanding rectfied though.. :) – PSL Sep 19 '14 at 04:28
1

This is the code of the ngStyle directive:

var ngStyleDirective = ngDirective(function(scope, element, attr) {
  scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
    if (oldStyles && (newStyles !== oldStyles)) {
      forEach(oldStyles, function(val, style) { element.css(style, '');});
    }
    if (newStyles) element.css(newStyles);
  }, true);
});

Notice that there is a scope.$watch for attr.ngStyle, that watch is what is making it trigger twice.

For instance, if you try to do the same thing using ngInit, you will notice that the function only gets called once. Now, lets have a look at the code of the ngInit directive, it looks like this:

var ngInitDirective = ngDirective({
  priority: 450,
  compile: function() {
    return {
      pre: function(scope, element, attrs) {
        scope.$eval(attrs.ngInit);
      }
    };
  }
});

Notice that there is no watch in this directive.

Josep
  • 12,926
  • 2
  • 42
  • 45