Counting Angular's $digest Cycles

heckascript picture heckascript · Dec 13, 2014 · Viewed 7.5k times · Source

tl;dr:

I want to have angular trigger css animations on page load. Is there a way to count angular's digest cycles within say, a controller or directive?


long version:

I have some angular animations which I want to run when the page loads, using ng-enter, ng-leave, ng-move and so on... with an ng-repeat directive.

As of 1.3.6, I know that angular waits to apply any animations until after 2 digest cycles occur, so these animations aren't happening at all because the data is (almost always)loaded into the view on the first digest cycle of my application. (sauce: https://docs.angularjs.org/api/ngAnimate#css-staggering-animations)

I'm wondering if there's some way that I can count digest cycles and either trigger the animations, or load the data in after the 2nd digest cycle?

Also, if I wait until 2 digest cycles, is there a risk that the second cycle wont occur in some instances meaning that my data wouldn't load into the view? If this is the case, is there a way that I can guarantee that at least 2 digest cycles will occur every time?

As a temporary fix, I'm using $timeout to load my data in after 500ms, but I know this is a really bad idea.


relevant code:

(changed some of the names of certain things because of an NDA on this project)

html:

<div ng-repeat="pizza in pizzas" class="za" ng-click="bake(pizza)"></div>

css/sass (browser prefixes removed for brevity):

.za {
  //other styles

  &.ng-enter,
  &.ng-leave,
  &.ng-move {
    transition: all 1s $slowOut;
    transform: translate(1000px, 0px) rotateZ(90deg);
  }
  &.ng-enter,
  &.ng-leave.ng-leave-active
  &.ng-move, {
    transform: translate(1000px, 0px) rotateZ(90deg);
  }
  &.ng-enter.ng-enter-active,
  &.ng-leave,
  &.ng-move.ng-move-active {
    transform: translate(0, 0) rotateZ(0deg);
  }
  &.ng-enter-stagger,
  &.ng-leave-stagger,
  &.ng-move-stagger {
    transition-delay: 2s;
    transition-duration: 0s;
  }
}

js:

// inside a controller
timeout(function() {
  scope.pizza = [ // actually injecting 'myData' and using `myData.get()` which returns an array of objects
    {toppings: ['cheese', 'formaldehyde']},
    {toppings: ['mayo', 'mustard', 'garlic']},
    {toppings: ['red sauce', 'blue sauce']}
  ];
}, 500);

Answer

floribon picture floribon · Feb 24, 2015

As pointed out in the documentation:

If you want to be notified whenever $digest is called, you can register a watchExpression function with no listener. (Since watchExpression can execute multiple times per $digest cycle when a change is detected, be prepared for multiple calls to your listener.)

So you can count the $digest with the following code:

var nbDigest = 0;

$rootScope.$watch(function() {
  nbDigest++;
});


Update: illustration as to why you cannot rely on HTML, if you look at your dev console you will see Angular complaining about not being able to end a digect cycle (abortion after 10 cycles)

angular.module("test", []).controller("test", function($scope) {
  
  $scope.digestCount = 0;

  $scope.incrementDigestCount = function() {
      return ++$scope.digestCount;
  }
  
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="test" ng-controller="test">
  <div>{{incrementDigestCount()}}</div>
</body>