Как использовать trackBy с ngFor

Я не могу понять, что я должен вернуть из trackBy. Основываясь на примерах, которые я видел в Интернете, я должен вернуть значение некоторого свойства на объект. Правильно ли это? Почему я получаю индекс в качестве параметра?

Например, в следующем случае:

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>

Объекты, показанные в шаблоне, все еще обновляются, несмотря на то, что имя не изменилось. Зачем?

Ответ 1

На каждом ngDoCheck вызванном для директивы ngForOf Угловая проверка, какие объекты изменились. Он использует разные для этого процесса, и каждый из них использует функцию trackBy для сравнения текущего объекта с новым. Функция trackBy умолчанию отслеживает элементы по идентификатору:

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

Он получает текущий элемент и должен возвращать некоторое значение. Затем значение, возвращаемое функцией, сравнивается со значением, возвращаемым этой функцией в последний раз. Если значение изменится, разница изменится. Поэтому, если функция по умолчанию возвращает ссылки на объекты, она не будет соответствовать текущему элементу, если ссылка на объект изменилась. Таким образом, вы можете предоставить свою настраиваемую функцию trackBy, которая вернет что-то еще. Например, некоторое ключевое значение объекта. Если это ключевое значение соответствует предыдущему, то Angular не обнаружит изменения.

Синтаксис ...trackBy:userByName больше не поддерживается. Теперь вы должны предоставить ссылку на функцию. Вот основной пример:

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; 
  }

Хотя ссылка на объект изменяется, DOM не обновляется. Вот плункер. Если вам интересно, как работает ngFor под капотом, прочитайте этот ответ.

Ответ 2

Поскольку эта тема все еще активна & найти четкий ответ сложно, позвольте мне добавить несколько примеров в дополнение к ответу @Max:

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;
   }

Давайте отобразим array на 3 деления, используя *ngFor.

app.component.html

Пример *ngFor без trackBy:

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

Что произойдет, если мы нажмем кнопку foo?

→ 3 div будут обновлены. Попробуйте сами, откройте консоль, чтобы проверить.

Пример *ngFor с trackBy:

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

Что произойдет, если мы нажмем кнопку foo?

→ Только первый div будет обновлен. Попробуйте сами, откройте консоль, чтобы проверить.

А что если мы обновим первый объект вместо всего объекта?

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

→ Здесь нет необходимости использовать trackBy.

Это особенно полезно при использовании подписки, которая часто выглядит как то, что я схематизировал с array. Так это будет выглядеть так:

   array = [];
   subscription: Subscription;

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

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

Из документации:

Чтобы избежать этой дорогой операции, вы можете настроить по умолчанию алгоритм отслеживания. предоставив опцию trackBy для NgForOf. trackBy принимает функцию с двумя аргументами: index и item. Если дается trackBy, angular треки изменяются на возвращаемое значение функция.

Подробнее читайте здесь: https://angular.io/api/common/NgForOf

Найдите мой оригинальный ответ здесь: fooobar.com/questions/182993/...