ng-repeat with track by over multiple properties - angularjs

I have a problem with angular ng-repeat directive.
Currently I work on some project where from the API I get a list of items (some times it could be 1k items) and this list should be refreshed every 5 seconds (it is monitoring related project).
When the list length is a little bigger the website while re-rendering DOM could "slow". It comes out that angular regenerate the whole DOM (but 95% of item are the same ! )
One of the possible approach is to set "track by" expression for example to item.id. But here comes another problem, I also want regenerate items when for example descriptions was changed by other user. Since track by is expression to item.id changes in item.description didn't update item in DOM.
There is way to track by over multiple properties? Maybe some function?
Or maybe do comparison by "hand" ?
Any ideas, code samples I would appreciate :)
UPDATE
what I discover when I set track by to item.id angular didn't re-crete html for items, just update value in already created element and it seems to be "faster" then removing and creating.
Previously I though a little bit different.
FIX
For those who are looking for better performance over >1k items in ng-repeat USE track by item.id it will boost your performance ;)

You do not need to create a function to handle track by multi properties.
You can do:
<div ng-repeat="item in lines track by item.id+item.description">

As the comment suggested you could try something like this:
<select ng-model="item" ng-options="item.id as item.description for item in items track by itemTracker(item)">
In your controller:
$scope.itemTracker= function(item) {
return item.id + '-' + item.description;
}
This might help with the number of DOM elements being re-rendered when the list changes.

Based my knowledge, the angularjs model is bind to the ui view, so the model will rerender via $apply or $digest once the value changed. so in your case, u wan bind the model value to ui view but also do not want to re-render the view if the value has not change,that is impossbile. this is what i know.
however, u can just manipulate the dom element. for example
store the data to a variable
var x = [{id:"id1",value:"v1"},{id:"id2",value:"v2"}]
in html, manual append or using directive to append, then assign the id to the element,
<div id="id1">v1</div>
check and compare the value, based ur needs.
once found, then angular.element("#yourid").text()
this will solve your browser resources consume problems.

Related

ng-repeat view "lagging" behind the actual model?

HTML:
{{vm.regions}}
<div ng-repeat="region in vm.regions">
{{region}}
</div>
On button presses, the model a.k.a vm.regions gets updated in the controller. For example vm.regions = [].
I can see that the array {{vm.regions}} is instantly updated, but the elements in the div take at least a second to update, meaning you can see old elements for a bit in the newly updated list, for example.
What is causing this?
It is in AngularJs Best Practices to always add "track by $index" in order to inprove performances.
{{vm.regions}}
<div ng-repeat="region in vm.regions track by $index">
{{region}}
</div>
Link : https://docs.angularjs.org/api/ng/directive/ngRepeat
Try using vs-repeat. It applies virtual scrolling to ng-repeat which increases its performance drastically even if you are not using one time binding.
http://kamilkp.github.io/angular-vs-repeat/#?tab=8

When not to use 'track by $index' in an AngularJS ng-repeat?

I recently got the console error `
Error: [ngRepeat:dupes] duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys...
— AngularJS Error Reference - ngRepeat:dupes
which I then used 'track by $index' and the issue was solved...
But that got me thinking... is there a reason why you wouldn't want to use track by $index in an ng-repeat?
I've read SO questions like this one as well as other articles, but it seems like almost all of the articles only talk about the advantages of using 'track by'.
Can someone list the disadvantages and give an example of when you wouldn't want to use track by $index?
It has a disadvantage,
'track by' expression tracks by index in the array. It means that as long as the index stays the same, angularjs thinks it's the same object.
So if you replace any object in the array, angularjs think it didn't change because the index in the array is still the same. Because of that the change detection wont trigger when you expect it would.
Take a look at this example
Try to change the name, nothing happens.
Remove track by index, it works.
add track by item.name, it still works.
There are multiple reasons to avoid track by $index
Avoid using track by $index when using one-time bindings
Avoid track by $index when there is a unique property identifier
Other Examples of problems with track by $index
Avoid using track by $index when using one-time bindings
The documentation specifically states that track by $index should be avoided when using one-time bindings.
From the Docs:
Avoid using track by $index when the repeated template contains one-time bindings. In such cases, the nth DOM element will always be matched with the nth item of the array, so the bindings on that element will not be updated even when the corresponding item changes, essentially causing the view to get out-of-sync with the underlying data.
— AngularJS ng-repeat Reference - Tracking and Duplicates
Avoid track by $index when there is a unique property identifier
Avoid track by $index when there is a unique property identifier to work with. When working with objects that are all unique, it is better to let ng-repeat to use its own tracking instead of overriding with track by $index.
From the Docs:
If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
— AngularJS ng-repeat Directive API Reference - Tracking
Other Examples of problems with track by $index
I have also seen problems when objects are added and removed from arrays of objects.
Problems with track by $index with Angular UI Carousel
Ng-Repeat showing irregular behavior with one time binding
Angular: Updating $scope with new data causes old data to remain when using ng-repeat
Trouble with AngularJS and Select binding not loading default value

Binding behavior of ng-repeat track by

Lets assume I am binding one big nested object to the $scope of the view shown in the code. Now, the value of an "e" object is updated. This would cause angular the check all bindings and update the DOM. If I used "track by" instead, in each ng-repeat directive, would that mean that only the binding for the "e" object would react and the dom for the "e" object be updated?
<div ng-repeat="a in b">
<div ng-repeat="c in a">
<div ng-repeat="d in c">
<div ng-repeat="e in d">
{{e.value}}<br>
</div>
</div>
</div>
</div>
The bindings will be checked no matter what, and updated only if different, per the digest cycle. As for re-building the DOM elements, Angular uses unique identifiers to determine whether each item in an ng-repeat already has a matching DOM element, or if it needs to render a new one.
By default, Angular creates and manages these unique identifiers under the hood, using the $id of each object (or $$hashKey).
track by was added later, as a way to tell Angular to use a unique identifier of your choice, rather than managing it under the hood.
This is useful when updating the data removes/changes the $id or $$hashKey, triggering unnecessary re-builds of each DOM element, even when the data didn't change at all.
Consider this example:
You have an ngRepeat which displays data records:
<li ng-repeat="item in data">{{item.value}}</li>
You use a service DataService to update your data, which has a fetch() method which retrieves data from an SQL database, and returns the records.
Updating the data in your $scope involves calling that service, and re-assigning your data variable to the result:
$scope.data = DataService.fetch();
That means, even if only one item was different, all the $id or $$hashKey properties are gone or different, and Angular will assume all items are new. It will re-build all the DOM elements from scratch.
However, since your data is from an SQL database, you already have a unique identifier (primary key), the id column. You could then change your ngRepeat to be:
<li ng-repeat="item in data track by item.id">{{item.value}}</li>
Now, instead of looking for $$hashKey, which gets lost every time you re-assign the data, Angular will use the property you told it to (item.id). Since that property does persist across re-assigning the variable, the list is once again optimized, because Angular will only re-build DOM elements for new items.

NG-Repeat doesn't update cleanly when scope is updated

So I have a list of workspaces:
<li class="workspace-object" ng-repeat="w in workspaces | filter:searchQuery" ng-click="selectWorkspace(w)">
{{w.name | titleCase }}
</li>
That List gets updated in the database by some function, and then I call the loadWorkspaces function below, to re-populate the $scope.workspaces object
$scope.loadWorkspaces = function() {
APIService.getWorkspaces().then(function(data){
$scope.workspaces = data;
});
}
When this happens, the $scope.workspaces, when logged out, reads the right updated information immediately, however, the ng-repeat DUPLICATES, before it updates to the proper list. I have no idea why this happens. Any ideas?
In this example, I am updating the workspace title, and that update runs the loadworkspaces function.
Try this ...
w in workspaces track by $index
When the contents of the collection change, ngRepeat makes the corresponding > changes to the DOM:
When an item is added, a new instance of the template is added to the DOM.
When an item is removed, its template instance is removed from the DOM.
When items are reordered, their respective templates are reordered in the DOM.
By default, ngRepeat does not allow duplicate items in arrays. This is because when there are duplicates, it is not possible to maintain a one-to-one mapping between collection items and DOM elements.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.
For example, you may track items by the index of each item in the collection, using the special scope property $index
Accepted answer did not help me (Using Angular 1.1.10), my repeat was still updating delayed. I struggled for ages before I got it working.
Instead of repeating my custom directive like so;
<my-directive ng-repeat="item in myarray track by item.id" some-binding="item"></my-directive>
I (magically) got it working after moving the ng-repeat to a (non-directive) parent element, like so:
<div ng-repeat="item in myarray track by item.id">
<my-directive some-binding="item"></my-directive>
</div>

Understanding bindonce limitations

I've been reading about bindonce as a way of reducing watches and enhance performance. In an effort to better understand the package i made an example with ng-repeat.
JSBIN here
Without bindonce I'm getting 103 watches, 100 list items + 2 buttons.
Using bindonce i'm getting 3 watches, 2 bottons + 1 fort the list.
If I understood binonce correctly, it removes the watch once the binded object is resolved and rendered. So,
How is it possible that using bindonce, changes made to the object are still reflected in the DOM ?
There's a hint in the documentation:
Now this example uses 0 watches per person and renders exactly the same result as the above that uses ng-. *(Angular still uses 1 watcher for ngRepeatWatch)
The key is that Angular still keeps a watch on ngRepeat, so if the array changes ngRepeat will re-render the array and the bindonce functionality is re-applied.
I've updated your jsbin example here to better illustrate this http://jsbin.com/xugemico/2/edit
Notice the following addition:
<p>
Bindonce: first item:
<span bindonce="arr" bo-bind="arr[0]"></span>
</p>
The above code utilizes bindonce on the first array item without ngRepeat's watch in play, you'll see that the value is not updated as per the bindonce inside ngRepeat.

Resources