1

I am new to AngularJS and I am trying to learn a bit more about the possibilities with ng-repeat. I have put together some code to try and learn a bit more but I have hit some issues that I don't understand.

Here is the code:

The HTML:

<ul class="list-unstyled">
    <li ng-repeat="worker in workers">
        <section class="well col-md-6 col-md-offset-3">
            <h1 class="h3 col-md-6 text-right" style="margin-top: 0"><a>{{worker.worker_name}} {{ getTipCount(worker.id) }}</a></h1>
        </section>
    </li>
</ul>

And in my Controller:

$scope.getTipCount = (workerId) ->
      WorkerTips = $resource("/workers/#{workerId}", { workerId: "@id", format: 'json' })
      json = WorkerTips .get ((res) ->
         $scope.workertipscount = res.worker.tips.lengh
      )

In the JSON each worker object has an array of tips and what I am trying to do is to get the count of how many tips each worker has. To do this I need to get the id from the worker through ng-repeat and then pass that to a function that then gets me the count of tips from the JSON and displays it on the page.

However when I try to do this it looks like the page goes into an infinite loop and I get the following error:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!

So I'm guessing my approach to pulling information out of a element in an ng-repeat and then sending it to a function that also gets sent to the ng-repeats view is incorrect? If so what is the correct approach to this for AngularJS?

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191
  • Is this a typo or incorrect in your code? - `res.worker.tips.lengh` – haxtbh Feb 10 '15 at 13:41
  • Why don't you calculate each workers tip on load of the controller first and then do something like `getWorkersTip(id)` instead of send a request on every single click? – dcodesmith Feb 10 '15 at 13:44

1 Answers1

5

The issue is that you are calling a function, which will result in a modification of a bound value on $scope, which in turn will execute the function again... etc. etc...

You should change this little bit of code:

{{ getTipCount(worker.id) }}

To something like this:

{{ workertipscount }}

Also, this appears to be a global variable you are setting here:

$scope.workertipscount = res.worker.tips.lengh

Instead, you could set this directly onto the worker object itself:

<!-- Send in the entire worker object -->
ng-click="getTipCount(worker)"

//Now you can set properties directly on
// the worker
$scope.getTipCount = (worker) ->
      WorkerTips = $resource("/workers/#{worker.id}", 
          { workerId: "@id", format: 'json' })

      json = WorkerTips.get ((res) ->
         worker.tipCount = res.worker.tips.length;
      )

<!-- And then just bind to that property -->
{{ worker.tipCount }}

This is just a suggestion, and one you certainly don't have to follow, but I've found that this approach makes life a lot easier.

Update:

Based on your comment below, I would recommend simply pre-computing this information when the collection loads. I'm assuming you are loading the set of workers from an AJAX call, so you could have something like this:

someMethodToGetWorkers()
  .then(function(workers){
     $scope.workers = workers;
  })
  .then(function(){
     angular.forEach($scope.workers, function(worker){
        //Make call to populate worker tips
     });
  });

This will go off an populate each workers tips, and if you save them to the worker object itself, then you can bind directly to it in your ng-repeat as I mentioned above.

As a side note, making this many trips back to the server is pretty inneficient, especially if you have a large collection because the browser caps you at 6 concurrent requests to the same domain.

You might look at reworking your API that returns the list of workers in the first place to simply go ahead and include the data for their tip count.

Explanation:

Angular uses dirty checking. Which means that it compares old values to new values in order to determine whether or not an action should take place. It accomplishes this by way of a $digest loop.

The $digest loop spins over each value in $scope and compares it to the old value based on what $watchers have been created. Any time you use the {{ }} syntax you are creating a watcher.

Angular keeps performing this loop until there are no detected changes.

In the case of function expressions, Angular has no way to know if the value has changed without executing the function... and in this case it results in a dirty state. Thus, the process is repeated ad infinitum until the failsafe of 10 iterations kicks in and Angular pukes.

In general it's best to avoid function expressions like {{ foo() }} because they will be executed every single loop of the $digest.

Josh
  • 44,706
  • 7
  • 102
  • 124
  • Thanks @Josh this is really good info. The one thing is that the ng-click I had in my code was only as a test, is it possible to do what you describe without using the ng-click? – Donal Rafferty Feb 12 '15 at 10:33
  • As in I need to pull the id from the Worker on each iteration of the repeat and pass it to the function but if I shouldn't be calling a function in the ng-repeat what is the best way to do this in Angular? – Donal Rafferty Feb 12 '15 at 12:02