1

The following HTML code populates a ul with 21 phones:

<li ng-repeat="phone in phones" ng-class="{'digestTest': countDigestOccurences(phone) }">
  <p>{{phone.snippet}}</p>
</li>

countDigestOccurences is a JavaScript method which uses a dictionary to keep track of how many times countDigestOccurences() is called per phone.

$scope.countDigestOccurences = function(phone){
  var phoneFound = false;     
  $.each($scope.digestOccurencesPerPhone, function(){
      if(this.phone.id == phone.id){
        phoneFound = true;
        this.occurences++;
      }
  });

  if(!phoneFound)
  { 
    $scope.digestOccurencesPerPhone.push({
      phone: phone,
      occurences: 1
    });
  }
}

Through this method I can clearly see that countDigestOccurences is called 4 times per phone. I can not, for the life of me, figure out why it's called 4 times.

enter image description here

Update:

Number of cycles will remain 4 even if the Phone item's HTML is as follows:

    <li ng-repeat="phone in phones "
        class="thumbnail phone-listing" ng-class="{ 'digestTest': countDigestOccurences(phone),  'digestTestAgain': randomMethodDoesNothing() }">
      <p>{{phone.snippet}}</p>
    </li>
Halaster
  • 1,442
  • 1
  • 17
  • 27
  • `ng-class` is evaluated in every digest cycle, not only when inserting the item in the `ng-repeat` – devqon Sep 18 '15 at 12:00
  • But why would there be 4 digest cycles for this? – Halaster Sep 18 '15 at 12:02
  • what is `$.each`? I've not seen that before. – Joe Lloyd Sep 18 '15 at 12:05
  • @JoeLloyd looks like jQuery each function to me – Magus Sep 18 '15 at 12:06
  • @JoeLloyd http://api.jquery.com/jQuery.each/ – charlietfl Sep 18 '15 at 12:06
  • @Magus cheers for that. many consider it bad practice to use jQuery and Angular together. To reduce bugs in your code I would suggest dropping jQuery and coming up with an angular way to fulfil your requirements – Joe Lloyd Sep 18 '15 at 12:09
  • @JoeLloyd use of utility like `$.each` will have no impact on digests. – charlietfl Sep 18 '15 at 12:11
  • Would be far far more efficient to map your phone counts to hashmap once instead of calling your function within `ng-class`. Digests do run many times and what you are doing is expensive and inefficient. Read up on digests cycles to understand why they run numerous times...and issues within your code that can cause them to run more than needed – charlietfl Sep 18 '15 at 12:14
  • @charlietfl thats fair enough. I was just generalizing based on a few posts I have read. – Joe Lloyd Sep 18 '15 at 12:28
  • 1
    Thats how angular works, angular will run whenever they want. http://stackoverflow.com/questions/17164230/angular-scope-function-executed-multiple-times, http://stackoverflow.com/questions/28615012/controller-function-called-multiple-times, http://stackoverflow.com/questions/14987277/function-called-multiple-times-in-angularjs-repeat-section, https://www.google.com/search?q=angular+function+multiple+times – YOU Sep 18 '15 at 12:28
  • @JoeLloyd yes, there are often ways to work around jQuery, however there are lots of use cases where jQuery can be helpful in angular and it integrates very cleanly with angular. Before over generalizing take a look at very beginning of `angular.element` docs https://docs.angularjs.org/api/ng/function/angular.element – charlietfl Sep 18 '15 at 12:33

2 Answers2

4

when Angular compiles and see an expression on the view, like ng-class="function()", ng-model="toto", a $watch is created for it. At every digest cycle, the watches are evaluated by the dirty checking to determine if there is any change in the model.

So in your ng-repeat, you have : one watcher on the phones collection, one watcher on each phone instance and one watcher on the function. As the function on the view isn't a scope variable, angular can't know if the result of the function has changed (you may affect an other scope variable in the function) and so, it reevaluate the function result for each digest cycle.

So you have phones + phone + function + last digest cycle to verify everithing it's ok : 4 cycles

A good practice is to not use function in the view except if rare cases. Instead, store the result of the function in a scope variable and render this variable in the view.

Update :

Due to the discussion bellow, note that only one watch si created for the ng-class directive and it correspond to the value of ng-class. I.e., with : ng-class="{'toto' : functionOne(), 'titi' : functionTwo()}", the watch is on : {'toto' : functionOne(), 'titi' : functionTwo()}. Issued from the AngularJs directive code : scope.$watch(attr[name], ngClassWatchAction, true);

matiou
  • 156
  • 1
  • 7
  • If that were the case, wouldn't the code I put in the bottom of answer now increase the cycles from 4? Code isn't up to standards because this is a project testing digest cycle – Halaster Sep 18 '15 at 13:31
  • I don't see why the code you added would increase the number of digests Lucas. – IfTrue Sep 18 '15 at 13:37
  • Updated again for clarity's sake. I have introduced a new function inside ng-class. Shouldn't this add another watcher and thus another cycle? – Halaster Sep 18 '15 at 13:40
  • No, because you're always working on the same phone instance, even if you add phone.imageUrl, phone.id,... this is the phone object watched and not its attributes – matiou Sep 18 '15 at 13:41
  • Yes, however I have now added another function. Doesn't a function need another cycle from what I understood? – Halaster Sep 18 '15 at 13:41
  • Looking at the AngularJs code of the directive, this is the value of the ng-class which is watched : scope.$watch(attr[name], ngClassWatchAction, true); (line 18382, AngularJS v1.2.26) and not each parameter – matiou Sep 18 '15 at 13:51
  • Just switched to ng-show instead of ng-class and the occurrences went down to 3. Thanks for the info! – Halaster Sep 18 '15 at 13:53
0

How many ajax calls did do you have via $http? Each of them would trigger a $digest. Also, if something changed (and it has, the new data arrived), another $digest will run to make sure it covers everything.

To avoid this add a boolean on an ng-if on a parent element and set it to true once all ajax calls have arrived (see $q).

Dragos Rusu
  • 1,508
  • 14
  • 14