How to use trackBy with ngFor

2020-01-28 05:38发布

I can't really understand what I should return from trackBy. Based on examples I've seen on the web, I should return the value of some property on the object. Is it correct? Why do I get index as a parameter?

For example, in the following case:

constructor() {
    window.setInterval(() => this.users = [
            {name: 'user1', score: Math.random()},
            {name: 'user2', score: Math.random()}],
        1000);
}

userByName(index, user) {
    return user.name;
}

...

<div *ngFor="let user of users; trackBy:userByName">{{user.name}} -> {{user.score}}</div>

The objects shown in template are still updated despite the name being unchanged. Why?

4条回答
祖国的老花朵
2楼-- · 2020-01-28 06:07

else you can use

*ngFor="a of array; index as i;"

and

[attr.data-target]="'#test' + i"

and

name="test{{i}}
查看更多
Deceive 欺骗
3楼-- · 2020-01-28 06:12

On each ngDoCheck triggered for the ngForOf directive Angular checks what objects have changed. It uses differs for this process and each differ uses trackBy function to compare the current object with the new one. The default trackBy function tracks items by identity:

const trackByIdentity = (index: number, item: any) => item;

It receives the current item and should return some value. Then the value returned by the function is compared against the value this function returned the last time. If the value changes, the differ reports a change. So if the default function returns object references, it will not match the current item if the object reference has changed. So you can provide your custom trackBy function that will return something else. For example, some key value of the object. If this key value matches the previous one, then Angular will not detect the change.

The syntax ...trackBy:userByName is no longer supported. You must now provide a function reference. Here is the basic example:

setInterval( () => {
  this.list.length = 0;
  this.list.push({name: 'Gustavo'});
  this.list.push({name: 'Costa'});
}, 2000);

@Component({
  selector: 'my-app',
  template: `
   <li *ngFor="let item of list; trackBy:identify">{{item.name}}</li>
  `
})
export class App {
  list:[];

  identify(index, item){
     return item.name; 
  }

Although the object reference changes, the DOM is not updated. Here is the plunker. If you're curious how ngFor works under the hood, read this answer.

查看更多
男人必须洒脱
4楼-- · 2020-01-28 06:17

This angular NgFor document will help you. https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

Below example for your code

<div *ngFor="let user of users; trackBy:user?.name">
 {{user.name}} -> {{user.score}}
</div>
查看更多
ら.Afraid
5楼-- · 2020-01-28 06:20

As this topic is still active & find a clear answer is difficult let me add few examples in addition to @Max's answer:

app.component.ts

   array = [
      { "id": 1, "name": "bill" },
      { "id": 2, "name": "bob" },
      { "id": 3, "name": "billy" }
   ]

   foo() {
      this.array = [
         { "id": 1, "name": "foo" },
         { "id": 2, "name": "bob" },
         { "id": 3, "name": "billy" }
      ]
   }

   identify(index, item) {
      return item.id;
   }

Let's display the array into 3 div using *ngFor.

app.component.html

Example of *ngFor without trackBy:

<div *ngFor="let e of array;">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>

What happend if we click on foo button ?

→ The 3 divs will be refreshed. Try it yourself, open your console to verify.

Example of *ngFor with trackBy:

<div *ngFor="let e of array; trackBy: identify">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>

What happend if we click on foo button ?

→ Only the first div will be refreshed. Try it yourself, open your console to verify.

And what if we updated the first object instead of the whole object ?

   foo() {
      this.array[0].name = "foo";
   }

→ There is no need to use trackBy here.

It's especially usefull when using Subscription which often looks like what I schematized with array. So it would looks like:

   array = [];
   subscription: Subscription;

   ngOnInit(): void {
      this.subscription = this.fooService.getArray().subscribe(data => {
         this.array = data;
      });
   }

   identify(index, item) {
      return item.id;
   }

From documentation:

To avoid this expensive operation, you can customize the default tracking algorithm. by supplying the trackBy option to NgForOf. trackBy takes a function that has two arguments: index and item. If trackBy is given, Angular tracks changes by the return value of the function.

Read more here: https://angular.io/api/common/NgForOf

Find my original answer here: https://stackoverflow.com/a/57890227/9753985

查看更多
登录 后发表回答