8

I'm trying to build an animation on some phrases that will be displayed on the site main page, in a random position and with fade and translate effects.

I would achieve this using ng-style attribute inside an ng-repeat attribute and setting the ng-style value calling a JavaScript function defined inside the HomeController.

Using this approch cause angular to throw the exception: $rootScope:infdig error 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations

I read so much about this but no solution has solved my case. Anyone could help me?

Here is a part of index.html:

<div class="phrasesContainer" animate-phrases="">
      <h3 class="flying-text" ng-repeat="phrase in Phrases" ng-style="getTopLeftPosition()">{{phrase}}</h3>
    </div>

Here is the controller function:

$scope.getTopLeftPosition = function() {
var top = randomBetween(10, 90);
var left = getRandomTwoRange(5, 30, 70, 80);

return {
  top: top + '%',
  left: left + '%'
};

}

Here is a demo: http://plnkr.co/edit/8sYks589agtLbZCGJ08B?p=preview

Androidian
  • 1,035
  • 1
  • 16
  • 40
  • `getTopLeftPosition()` is being called on every digest because it never stabilizes. It always returns a different value. Why not move the style related code into your directive? – Anid Monsur Sep 22 '15 at 16:46

4 Answers4

4

@Hieu Le hinted to the problem, but your issue is that since you are always returning a random position in your getTopLeftPosition function, angularjs digest loop will get called every time to actually propagate the changes to the watchers. This caused it to keep running over and over.

What you can do is to pre-calculate your random positions and then use that in your html.

For example, in your activate function you can do something like this:

  function activate() {
    $scope.Phrases = ["Phrase 1", "Phrase 2", "Phrase 3", "Phrase 4", "Phrase 5", "Phrase 6"];
    $scope.PhrasesAndPositions = $scope.Phrases.map(function(phrase){
      return {
        phrase: phrase,
        position: getTopLeftPosition()
      }
    });
  }

And then you can change your html to something like this:

    <div class="phrasesContainer" animate-phrases="">
      <h3 class="flying-text" ng-repeat="pap in PhrasesAndPositions" ng-style="pap.position">{{pap.phrase}}</h3>
    </div>

Here is the working plunk with my changes: http://plnkr.co/edit/FD9hYX9Q5wUkW2q7y86M?p=preview

JoseM
  • 4,302
  • 2
  • 24
  • 35
  • I think the OP wants a phrase to have a new position every time it reappears. Not clear from the question though. Nice answer! – Anid Monsur Sep 22 '15 at 17:01
  • Great suggestion @JoseM, the problem with this solution is that it removes the randomness of the position. When it repeats again, all phrases will have the same position and not randomized – Hieu Le Sep 22 '15 at 17:02
  • Thanks! This solution solve my problem!! @Hieu Le Also my solution does not set random positions at each iteraction (because of the ingdig exception). However, the Anid Monsur solution allow this behaviour. I can't accept both answers but I would it (http://stackoverflow.com/questions/32721766/angularjs-rootscopeinfdig-error-when-calling-a-ng-style-function-inside-ng-re/32722877#32722877) – Androidian Sep 23 '15 at 08:02
2

Here's a solution where I moved your style generation into the directive. The position is being set right before showing the element. Since this is a CSS change, I modified the styling as well so that the position does not transition.

Here's the directive. The code I've excluded has not been changed:

app.directive('animatePhrases', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      setTimeout(function() {
        ...
      }, 1000);

      function changeText() {
        var currentActive = $('.phrasesContainer .active');
        var nextActive = currentActive.next();
        currentActive.toggleClass('active');

        if (nextActive.length == 0)
          nextActive = $('.phrasesContainer .flying-text').first();

        nextActive.css(getTopLeftPosition()); // Add this
        nextActive.toggleClass('active');

        setTimeout(changeText, 5000);
      }

      function getTopLeftPosition() {
        ...
      }

      function getRandomTwoRange(firstStart, firstEnd, secondStart, secondEnd) {
        ...
      }

      function randomBetween(min, max) {
        ...
      }
    }
  };
});

CSS:

.flying-text {
    transition: opacity 2s ease-in-out, margin 2s ease-in-out;
    position: absolute;
    opacity: 0;
    font-size: 1.5em;
}

In your HTML, simply remove the ng-style.

Plunker: http://plnkr.co/edit/bZB3A5hD7Bc4r4pp1g7V?p=preview

Anid Monsur
  • 4,538
  • 1
  • 17
  • 24
  • Thanks! I will use that solution, the controller is cleaner :) Also @JoseM solution solve my problem (http://stackoverflow.com/questions/32721766/angularjs-rootscopeinfdig-error-when-calling-a-ng-style-function-inside-ng-re/32722186#32722186), it does not allow randomness at each iteraction but it's still a great solution if you want random position only at the start! – Androidian Sep 23 '15 at 08:14
0

this way you can manger your ng-style

$scope.getTopLeftPosition = function() {
$scope.top = randomBetween(20, 90);
$socpe.left = getRandomTwoRange(5, 30, 70, 80);

}

function getRandomTwoRange(firstStart, firstEnd, secondStart, secondEnd) {
var isLower = (Math.random() * 2) < 1;
if (isLower)
  return randomBetween(firstStart, firstEnd);
else
  return randomBetween(secondStart, secondEnd);
}

function randomBetween(min, max) {
 return Math.floor(Math.random() * (max - min + 1)) + min;
}


<body>
<div ng-controller="HomeController">
  <div class="row" style="height:100%">
    <div class="phrasesContainer" animate-phrases="">
      <h3 class="flying-text " ng-repeat="phrase in Phrases" ng-style="{ 'top' : top, 'left' : left }">{{phrase}}</h3>
    </div>
  </div>
</div>

Rubel hasan
  • 2,522
  • 1
  • 13
  • 22
0

I think the problem is that you cannot bind ng-style like that. It thinks that it needs to call getTopLeftPosition constantly.

I got it to kind of work here. The timing is a bit off but there are no more errors. Here I used $interval to repeat things:

Please view it here: http://plnkr.co/edit/B4iMZXMvZFoYMCKNvVrc?p=preview

  $scope.phrase_style = getTopLeftPosition();

  function change_phrase_style() {
      $scope.phrase_style = getTopLeftPosition();
  }

  function start_interval() {
      change_phrase_style();
      $interval(function(){
          change_phrase_style();
      }.bind(this), 5000); 
  }

  setTimeout(start_interval, 1000);

This might be related as well: angularjs infinite $digest Loop when no scope changes

Community
  • 1
  • 1
Hieu Le
  • 738
  • 4
  • 8