1
votes

In my factory's method, I return an array from the server with Angular's $resource:

var resource = $resource("vm", {
    session: function() {
        return Auth.token();
    },
    all: '@all',
    vmId: '@vmId'
}, {
    listVM: {
        method: 'GET',
        url: _baseUrl + 'vm/machines',
        isArray: true,
        cache: false
    }
}

...

_obj.getRunningVMs = function(all) {
    return resource.listVM({all: all}).$promise;
};

...

return _obj;

And in my HTML I have (with the service aliased as vms in the template):

<div ng-repeat="machine in vms.getRunningVMs(true) track by machine.id">{{machine.owner}} {{machine.host}}</div>

This caused an infinite digest error:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations:
[["fn: $watchCollectionWatch; newVal: 22; oldVal: 19"],
["fn: $watchCollectionWatch; newVal: 25; oldVal: 22"],
["fn: $watchCollectionWatch; newVal: 28; oldVal: 25"],
["fn: $watchCollectionWatch; newVal: 31; oldVal: 28"],
["fn: $watchCollectionWatch; newVal: 34; oldVal: 31"]]

which I know is caused by the fact that the method call returns a brand new array every time, which fails Angular's equality check for watched collections.

I tried track by:

<div ng-show="managing === 'machines'" ng-repeat="machine in vms.getRunningVMs(true) track by machine.id">{{machine.owner}} {{machine.host}}</div>

which caused the following error:

Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: machine in vms.getRunningVMs(true) track by machine.id, Duplicate key: undefined, Duplicate value: undefined

I know that id is defined, and I get the same error when tracking by $index.

Is track by not meant for dealing with functions that return new arrays, even with track by? I know it was originally meant to avoid needless DOM mutation, but it seems like it could easily be made to work in this situation.

1

1 Answers

1
votes

from http://www.bennadel.com/blog/2556-using-track-by-with-ngrepeat-in-angularjs-1-2.htm#comments_43733:

Another very important thing I just realized:

When using Angular Resources (resource.query(...)), the return value is actually a "future" and not the data itself, so in case the ng-repeat is bound to a property in the $scope that is actually a future, when replacing that future with a new one (by reloading the data for example) all the items will be destroyed and recreated even when using the "track by" mechanism. my guess is that it's because the future object is basically empty initially and so the track by mechanism can't find any item until it's actually filled.

As a workaround I use the success callback of the resource.query method and just set the actual result back to the property in the $scope.

something like:

myResource.query({}, function (data) {
    $scope.divisionItems = data;
});