简体   繁体   中英

Ng-repeat one-time binding and “track by” change

Our web app uses ngRepeat to display a list of items. The array and its objects are never changed, but values of the objects inside can be modified by the user.

We generate a unique trackId's for each item. This trackId is updated every time the item's values change.

We are also using the one-time binding syntax to reduce the number of watchers on the page (as it can quickly climb to the thousands).

However, this combination does not seem to actually work; If the item's trackId changes but the object's reference stays the same, the item is not re-rendered.

From the angularJS docs:

Custom Expression: It is possible to use any AngularJS expression to compute the tracking id, for example with a function, or using a property on the collection items. item in items track by item.id is a typical pattern when the items have a unique identifier, eg database id. In this case the object identity does not matter. Two objects are considered equivalent as long as their id property is same. Tracking by unique identifier is the most performant way and should be used whenever possible.

If this is the case, why is the item not destroyed and re-created when the trackId is modified?

For example:

$scope.friends = [
    {name:'John', age:25, id: 'John-25'},
    {name:'Mary', age:40, id: 'Mary-40'},
    {name:'Peter', age:85, id: 'Peter-85'}
];

$scope.change = function(i) {
    var f = $scope.friends[i];
    f.age = Math.floor(Math.random() * 100);
    f.id = f.name + '-' + f.age;
};

And

 <div ng-repeat="friend in friends track by friend.id">
    <div ng-bind="'Track id: ' + friend.id"></div>
    <div ng-bind="'Regular binding:' + friend.age"></div>
    <div ng-bind="::'One-time binding:' + friend.age"></div>
</div>

<button ng-click="change(0)">Change John's age</button>
<button ng-click="change(1)">Change Marys's age</button>
<button ng-click="change(2)">Change Peters's age</button>

In this demo I would expect the object to be destroyed when the trackId changes and the element should be re-rendered with the new one-time binding value.

https://plnkr.co/edit/Lklq3ZNDUuggjgwmkoxj?p=preview

Does anyone have any suggestions on a way to get around this? We absolutely cannot remove one-time binding for performance reasons. I have also looked into angular-bind-notifier but that will require every binding in the repeat to be updated since it cannot target specific rows.

Thanks

Keep the id on each friend constant.

If this id is truly just an ID for each friend, why would it need to change? If you're using that property for something more than an ID, maybe consider adding a new property on the object and keep the ID's constant.

If you don't want to do that, you could always create some helper function that stores a current friend's id-value in a dictionary with id keys. Then, you can just update or retrieve the value stored in that friend ID by friend.id .

ng-repeat is using $watchCollection internally which is a shallow watch on the array, so changing a property on one of the array items wont cause the ng-repeat to update. The docs are a bit misleading here.

As you guessed you need to change the object reference. After toggling the id replace the object with a shallow copy. This will will cause $watchCollection to detect the change and ng-repeat will then replace the item due to the id change.

$scope.friends = [
    {name:'John', age:25, id: 'John-25'},
    {name:'Mary', age:40, id: 'Mary-40'},
    {name:'Peter', age:85, id: 'Peter-85'}
];

$scope.change = function(i) {
    var f = $scope.friends[i];
    f.age = Math.floor(Math.random() * 100);
    f.id = f.name + '-' + f.age;

    $scope.friends[i] = Object.assign({}, $scope.friends[i]);
};

Updated plunker: https://plnkr.co/edit/s2ztGQ?p=preview

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM