1

I have been developing a few prototype apps in Angular, with various backends, including firebase and php/mysql. I'm pretty sure I'm not using a good organization pattern for development because of a couple of kluges I have to use in the code to avoid the "max digest iterations reached" exception.

I have a template with an ng-repeat, like this:

<ion-item data-ng-repeat="outcome in Outcomes.getAll()">...</ion-item>

It is supported by a service like this:

'use strict';

angular.module('app.outcomes', ['app.utility']).service('Outcomes', function ($http, Utility) { var svc = this; svc.outcomes = false;

/**
 * Initializes the outcomes array if it has not yet been initialized.
 *
 * @returns {Array}
 */
svc.initialize = function () {
    if (svc.outcomes === false) {
        // If this is not set to something immediately, repeated calls to 
        // digest occur during http call and digest overloads.
        svc.outcomes = true; 
        $http.get('/api/outcome/all').
            success(function (data, status, headers, config) {
                        svc.outcomes = data.result; // set data to real array value
                    }).
            error(function (data, status, headers, config) {
                  });
    }
};

/**
 * Called in ng-repeat so that it makes sure the data is initialized.  
 * An ng-init call does not work.
 * @returns {Array}
 */
svc.getAll = function () {
    svc.initialize();  // called constantly from ng-repeat, while array actually set
    return svc.outcomes;
};});

The controller is a one-liner that sets the $scope.Outcomes variable to the service.

This strategy works, because the digest loop tests the condition. What seems klugy to me is that I need to set the outcomes array to some unused value while I wait for the ajax call to return. Otherwise the digest loop is called and it tries to keep doing it for however long the call takes to return from the server.

Another related problem I have is that if I set the svc.outcomes array later, I will sometimes get the same error about too many digest iterations.

The questions I have are:

1) Is there a better way to organize the code? I need to be able to access that server data across multiple scopes.

2) Is there a way to ensure that the data array gets initialized without having to call the getAll() method in the ng-repeat loop? An ng-init call doesn't populate the data, so I can't just reference Outcomes.outcomes in the template.

What is strange to me is that I think the "max iterations" thing just started happening with using a minor update (1.3.3 to 1.3.6) to angular. That could be my imagination though. Any architectural suggestions would be appreciated.

2

tldr;

1) Yes, there's a better way.
2) Yes, you shouldn't be firing an HTTP request in an ng-repeat

Full Answer

You need to understand how ng-repeat works. Every single time the digest cycle runs, angular checks to see if the array in the ng-repeat has changed. And when you work with angular, I'd recommend that you think of the digest cycle as something that runs ALL THE TIME. Everything you do in angular fires a digest cycle. Clicked somewhere? The digest cycle just ran. Changed a value in an input somewhere? Digest cycle just ran. Hovered over something with an angular-bootstrap tooltip (because you thought those things are much prettier than titles)? Digest cycle just ran. Breathed too hard? Digest cycle just ran.

That's why you'll often hear people say you shouldn't ng-repeat over functions. Not because that's a hard and fast rule, but because in general, you don't want to force angular to have to execute that function every single time anything happens on the page just to see if the return value changed. Do your self a favor and ng-repeat over an array, not a function that returns an array.

Which brings me to my next point. If you do ng-repeat over a function, make sure the function returns the same array every time. I don't mean an array with the same elements, or an array that looks the same, but the same javascript object. If you don't, you'll end up with a max digests reached error. That's because even though the contents of the array are the same, the actual objects are different.

So how should you be doing this?

In your controller, call the initialize method on the service. This will populate the outcomes array. In your ng-repeat, iterate over the outcomes array directly. To do this, you'll need to expose two things from your service: the initialize function (you already are doing this) and the outcomes array that's populated in the success method of your $http.get call. You can get rid of the getAll function.

Final Note

I'm assuming that you really only have to run the HTTP request once. If you have to run the HTTP request all the time to reflect changes on the server, you need to start thinking of other solutions to this problem (websockets, etc). You can't run an HTTP request inside the digest cycle and expect your app to respond normally.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.