3
votes

http://plnkr.co/edit/dBe36L6vwOZOykujFRfg

In the plunker, I'm getting the following angular error: "10 $digest() iterations reached. Aborting!"

The logic works when used in index.html:

<h1>(working) Fruit from main document: {{vm.randomFruit().name}}</h1>

But throws the error when I try to call similar code in the directive with an object passed in:

<h1>(causing error) Fruit from directive template: {{fruit.name}}</h1>

It also appears to not throw the error in the directive if I simply do this in the scope function:

//this works for both instances
return vm.fruits[0];

However when I touch $scope.fruits in any way, even just to copy it, it thows the error on the directive version.

//something about touching the array here exposes the error, even though it still works
var x = [];
angular.copy(vm.fruits, x);
return x[0];

Why is this error being thrown here? It appears to be some type of circular dependency, but why only on the directive version?

Is there a better way to use a directive, template, and passed-in parameter object that is more standard?

Error: 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations: [["fn: parentValueWatch; newVal: {\"name\":\"apple\"}; oldVal: {\"name\":\"apple\"}"],["fn: parentValueWatch; newVal: {\"name\":\"apple\"}; oldVal: {\"name\":\"apple\"}"],["fn: parentValueWatch; newVal: {\"name\":\"apple\"}; oldVal: {\"name\":\"apple\"}"],["fn: parentValueWatch; newVal: {\"name\":\"apple\"}; oldVal: {\"name\":\"apple\"}"],["fn: parentValueWatch; newVal: {\"name\":\"apple\"}; oldVal: {\"name\":\"apple\"}"]] at Error (native) at Object.$digest (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js:7925:19) at Object.$apply (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js:8097:24) at done (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js:9111:20) at completeRequest (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js:9274:7) at XMLHttpRequest.xhr.onreadystatechange (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js:9244:11)


Update 1

Updated aFruit() to randomFruit() in order to better demonstrate my issue:

$scope.randomFruit = function() {
//something about touching the array here exposes the error, even though it still works
var x = [];
angular.copy($scope.fruits, x);
//do some custom stuff here, sorting, etc. <whatever>
var randomIndex = Math.floor((Math.random() * x.length));
return x[randomIndex];
};

Update 2

Was told not to use $scope, so completely removed it from the controller. Still seeing the same error.

myApp.controller('FruitCtrl', function() {
  var vm = this;
  vm.fruits = fruits;

  vm.randomFruit = function() {
    //something about touching the array here exposes the error, even though it still works
    var x = [];
    angular.copy(vm.fruits, x);
    //do some custom stuff here, sorting, etc. <whatever>
    var randomIndex = Math.floor((Math.random() * x.length));
    return x[randomIndex];
  };
});
2

2 Answers

2
votes

The issue is with the approach, how the data is passed to the Angular. Angular has awesome data binding and it checks if the model is changed and updates the UI. Once the UI is updated, it checks again for the next round of changes. If data is changed all the time, it can stuck in the loop.

In this particular case the Angular does not understand that the data is the same. It receives new array on every digest loop and assumes that the data is changed, updates UI and starts checking again.

Make sure that you are not updating arrays from the UI on every $digest loop or returning new array on every $digest loop. e.g. This will call infinite loop:

$scope.aFruit= function() {
  return [
  {"name":"apple"},
  {"name":"orange"},
  {"name":"banana"},
];
};

This will work properly, because the reference to the array, stays the same

var myFruits =   return [
      {"name":"apple"},
      {"name":"orange"},
      {"name":"banana"},
    ]    
$scope.aFruit= function() {
    myFruits;
    };
-1
votes

Oh ok gotcha! There are two ways of doing this but I believe you're looking for this approach.

http://plnkr.co/edit/E1y9nqh0DdaHGW2hi4AM?p=preview

The thing is you're method is returning an object, but you want to grab the string value. So we change the method to this:

$scope.randomFruit = function() {
  var x = [];
  angular.copy($scope.fruits, x);
  var randomIndex = Math.floor((Math.random() * x.length));
  return x[randomIndex].name;
};

Next, we need to change our component bindings to accept strings with '@'

bindings: { fruit: '@' },

Last, we need to invoke the method in our template and use interpolation to set it as the value we pass in to the component:

<show-fruit fruit={{randomFruit()}} />

Hope this helps, again there's another approach but if you want to invoke your method in the template this is how you would have to do it.