What is "track by" in AngularJS and how does it work?
Asked Answered
C

4

26

I don't really understand how track by works and what it does.
My main goal is to use it with ng-repeat to add some precision.

Carolinecarolingian answered 22/9, 2016 at 13:16 Comment(0)
T
29

Using track by to track strings & duplicate values

Normally ng-repeat tracks each item by the item itself. For the given array objs = [ 'one', 'one', 2, 'five', 'string', 'foo'], ng-repeat attempts to track changes by each obj in the ng-repeat="obj in objs". The problem is that we have duplicate values and angular will throw an error. One way to solve that is to have angular track the objects by other means. For strings, track by $index is a good solution as you really haven't other means to track a string.

track by & triggering a digest & input focuses

You allude to the fact you're somewhat new to angular. A digest cycle occurs when angular performs an exhaustive check of each watched property in order to reflect any change to the correspodant view; often during a digest cycle it happens that your code modify other watched properties so the procedure needs to be performed again until angular detects no more changes.

For example: You click a button to update a model via ng-click, then you do somethings (i mean, the things you wrote in the callback to perform when an user makes a click), then angular trigger digest cycle in order to refresh the view. I'm not too articulate in explaining that so you should investigate further if that didn't clarify things.

So back to track by. Let's use an example:

  1. call a service to return an array of objects
  2. update an object within the array and save object
  3. after save service, depending on what the API returns, you may:
    1. replace the whole object OR
    2. update a value on the existing object
  4. reflect change in ng-repeat UI

How you track this object will determine how the UI reflects the change.

One of the most annoying UXs I've experienced is this. Say you have a table of objects, each cell has an input where you want to in-line edit those objects' properties. I want to change the value, then on-blur, save that object while moving to the next cell to edit while you might be waiting on the response. So this is an autosave type thing. Depending on how you setup your track by statement, you may lose current focus (e.g. the field you're currently editing) when the response gets written back into your array of objects.

Tiemroth answered 22/9, 2016 at 13:33 Comment(1)
@Kyll you were correct in making that edit. I had 5 other things going on in the background (like screaming children) while trying to write this. Thanks for fixing it.Tiemroth
D
16

When you add track by you basically tell angular to generate a single DOM element per data object in the given collection.

You can track by $index if your data source has duplicate identifiers.

If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.

Example:

[{id:1,name:'one'}, {id:1,name:'one too'}, {id:2,name:'two'}]

Try to use the duplicate values in ng-repeat, you will get an error such as:

Error: ngRepeat:dupes Duplicate Key in Repeater

To avoid this kind of problems you should use track by $index. For example:

<ul>
   <li ng-repeat="item in [1, 2, 3, 3] track by $index">
       {{ item }}
   </li>
</ul>

Here is how you would get $index in nested ng-repeat:

<div ng-repeat="row in matrix">
    <div ng-repeat="column in row">
      <span>outer: {{$parent.$index}} inner: {{$index}}</span>
    </div>
</div>

Here are some resources that may help you:

Diehard answered 22/9, 2016 at 13:24 Comment(2)
Codelord is one of the best Angular resources out thereFloater
"Try to use the duplicate values in ng-repeat, you will get an error" - actually if one will use your example [{id:1,name:'one'}, {id:1,name:'one too'}, {id:2,name:'two'}] and use ng-repeat="obj in objects" he won't get any error, only if two of the objects in the array are the exact same object (not only data, also the same reference when dealing with non primitive values) the error will occur.Pierian
F
3

You should use track by only if you need to go against the default behaviour of ng-repeat which is to remove duplicate items.
You can track the items using the scope property $index or specifying a custom function.

For instance:

<div ng-repeat="x in [42, 42, 43, 43] track by $index">
  {{x}}
</div>

Display all values of the array (42 is displayed twice).

For reference: https://docs.angularjs.org/api/ng/directive/ngRepeat

Fluoric answered 22/9, 2016 at 13:29 Comment(0)
U
0

Let's say, we have the following list:

<ul>
   <li ng-repeat="item in items">
       {{ item }}
   </li>
</ul>

where, an item has the following structure:

{ 'id'=>id, 'name'=>name, 'description'=>description }

There is no problem whatsoever in this list until we wish to update it. For our own convenience we replace the list of items, with another updated list of items, as such:

items = newItems;

However, in this new list, few items change. Most items remain the same. Unfortunately Angular does not know how to identify our items and map them to the respective <li> elements, so it just deletes all elements and creates them again. This is extremely performance-costly in some cases and here is where track by comes in use.

By adding the track by clause to the element

<li ng-repeat="item in items track by item.id">

we are informing Angular, that the unique identifier of our items is item.id. Now Angular knows not to recreate all items, but only items with new ids, and only updates the rest. The performance improvement is significant in most cases. Also, personally, I like that I can monitor my items easier on my browser's developer tools, because they don't disappear every time I update them.

Underwing answered 16/7, 2020 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.