Understanding the ngRepeat 'track by' expression - javascript

I'm having difficulties understanding how the track by expression of ng-repeat in angularjs works. The documentation is very scarce: http://docs.angularjs.org/api/ng/directive/ngRepeat
Can you explain what the difference between those two snippets of code is in terms of databinding and other relevant aspects?
with: track by $index
<!--names is an array-->
<div ng-repeat="(key, value) in names track by $index">
<input ng-model="value[key]">
</div>
without (same output)
<!--names is an array-->
<div ng-repeat="(key, value) in names">
<input ng-model="value[key]">
</div>

You can track by $index if your data source has duplicate identifiers
e.g.: $scope.dataSource: [{id:1,name:'one'}, {id:1,name:'one too'}, {id:2,name:'two'}]
You can't iterate this collection while using 'id' as identifier (duplicate id:1).
WON'T WORK:
<element ng-repeat="item.id as item.name for item in dataSource">
// something with item ...
</element>
but you can, if using track by $index:
<element ng-repeat="item in dataSource track by $index">
// something with item ...
</element>

a short summary:
track by is used in order to link your data with the DOM generation (and mainly re-generation) made by ng-repeat.
when you add track by you basically tell angular to generate a single DOM element per data object in the given collection
this could be useful when paging and filtering, or any case where objects are added or removed from ng-repeat list.
usually, without track by angular will link the DOM objects with the collection by injecting an expando property - $$hashKey - into your JavaScript objects, and will regenerate it (and re-associate a DOM object) with every change.
full explanation:
http://www.bennadel.com/blog/2556-using-track-by-with-ngrepeat-in-angularjs-1-2.htm
a more practical guide:
http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/
(track by is available in angular > 1.2 )

If you are working with objects track by the identifier(e.g. $index) instead of the whole object and you reload your data later, ngRepeat will not rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones.

Related

Getting DOM element in AngularJS directive

I´m trying to get a DOM element in order to change it inside a AngularJS1.0.6 directive.
HTML:
<li ng-repeat="car in cars" data-highlight="{{car.id}}">
Directive:
var iw = angular.element(document.querySelector('#iw-' + id));
console.log("iw=" + iw);
Please see the plunker for details: https://plnkr.co/edit/dP2cvut4f5ao5pFe6ka0?p=preview
After little research, I was able to make it work. Basically first issue comes when you use directive inside ng-repeat (also directive).
In this case directive's scope must be set for example like: scope: {car: '=highlight'}. This can desribe what happens. I would rather use <ul highlight="cars"></ul> along with directive's template like '<ul><li ng-repeat="car in cars"></li></ul>'. So ng-repeat would be inside directive.
When you get through, another issue comes in form of syncing two directives (different scopes). You need DOM ready to select element from another directive. I saved your compliled element to car object - it can be count as workaround that make it easier.
Forked plunker here
<li ng-repeat="car in cars track by $index" data-highlight="{{car.$index}}">
or
if whatever property exists, in your case, car must have id property

Performance of ng-repeat with dynamically generated array of data

Let's say I have an array of objects and that each object has a unique ID and a text property.
Now, let's say I have a ng-repeat like this one:
Controller
$scope.data = my_arr;
View
<div ng-repeat="for elem in data track by elem.id">
{{ elem.text }}
</div>
Special note on the track by.
What would happen if I assigned a completely new array to the data variable ($scope.data = new_arr;), if the content of this new array:
is completely identical to the previous one
it contains 1 new element
it contais all elements except 1
Will AngularJS be smart enough to:
not re-render all div elements
append/insert only the new div element to the DOM
hide/delete only the div that isn't contained in the new array
Since you’re using track by elem.id, Angular will reuse DOM elements. It will only add or remove one element to the document tree. Per the docs:
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.

Bindonce on ngRepeat

What is the effect of using the native bindonce on an ng-repeat object? For example:
ng-repeat="data in ::stuff"
Does this mean every item in 'stuff' has the watcher removed? Or do you still need to apply bindonce to every child bind in the repeat like this?
<div ng-repeat="data in ::stuff">
<span ng-bind="::data.thing"></span>
</div>
For data in ::stuff, the array is bound once and a $watcher is not created after bound the first time, and therefore any changes to that array will not update your ng-repeat's view.
However, unless you have ::data.thing changes to individual objects will still be registered. Those watchers belong to the object itself and not the shallow contents of the array.
See my plunkr below.
<iframe src="http://embed.plnkr.co/3gbmI2kqd3rT7z0GEyK7/"></iframe>

Best way to use ng-repeat in Angularjs for specific field

I am new to Angularjs and my app(built with Angular & Rails) is taking too much time to load the images/data. So I plan to optimize the code. Here is very quick and short question. What is the best way to use ng-repeat ? I planned to use track by in ng-repeat.
In my $scope.attributes I am getting thousands of data from server. In each there are many fields. But I wanted to use single field from those. SO I tried something like this:
<div ng-repeat="attribute in attributes track by attribute.name" >
<li> {{attribute.name}} </li>
</div>
Is this correct way? I have seen many examples everywhere use track by task.id So Can I use any specific field name instead of id ? Can I use filter also? what is the meaning of $$hashkey approach? I wanted to load images and it takes more time. Please want Experts advice.
if you track by .name it will use .name as the identifier, eg if the list changes it will find the ROW by .name
If the list will not change, look at one way binding
<div ng-repeat="attribute in ::attributes track by $index" >
<li> {{attribute.name}} </li>
</div>
-- I am sure that this will give you a DIV around the LI
Not related to your Q, but having just done some AngularJS performance tuning.
Consider using ng-if instead of ng-show / ng-hide , ng-show/hide will still produce the DOM , ng-if will not. (You may have done this already)

AngularJS: ngTransclude and access scope of ngRepeat

We are in the process of upgrading our application to Angular 1.3.0. In doing so, we ran into a few issues, most of which seem to boil down to the behavior of ngTransclude inside of ngRepeat.
We have a directive that repeats a bunch of items, with a container around them, but does not own the children of that container. For instance, here is a simplified example:
<div ng-controller="myController">
There are {{items.length}} items.
<div my-directive items="items">
This item's name is {{item.name}}
</div>
</div>
Internally, the directive contains <li ng-repeat="item in items" ng-transclude></li>, among other things.
Prior to the update, this worked fine. The repeated, transcluded elements are in a scope that inherits from the scope created by ngRepeat. As of the update, the items are in a scope that inherits from the controller, and as far as I can tell, there is no way to access the scope created by ngRepeat.
Here are two JS Bin examples:
Old (1.3.0-beta.1) behavior: http://jsbin.com/kalutu/1/edit
New (1.3.0) behavior: http://jsbin.com/gufunu/1/edit
How can I achieve the old behavior, or some semblance of it, in Angular 1.3.0? If this is the intended behavior of ngTransclude, how can I repeat a bunch of child nodes without knowing what they are?
https://github.com/angular/angular.js/issues/8182
It was decided for 1.3 that ng-trasclude would not pull scope from the directive. There is a work-around on the linked pages,
https://github.com/angular/angular.js/issues/7874
https://github.com/angular/angular.js/issues/7874#issuecomment-47647528
This is the expected behavior.

Categories

Resources