Как использовать 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>

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

8 ответов

Решение

На каждой ngDoCheck срабатывает для ngForOf Директива Angular проверяет, какие объекты изменились. Это использует отличается для этого процесса, и каждый отличается использует 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 работает под капотом, прочитайте этот ответ.

Поскольку эта тема все еще активна, и найти четкий ответ сложно, позвольте мне добавить несколько примеров в дополнение к ответу @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 блока будут обновлены. Попробуйте сами, откройте консоль, чтобы проверить.

Пример *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

Найдите мой исходный ответ здесь: /questions/33567218/kak-ispolzovat-trek-vnutri-ng-dlya-uglovyih-2/55202697#55202697

Вот что я использую в своих проектах, чтобы разрешить отслеживание по свойству итерированной модели без хлопот по написанию функции в классе компонента:

      import { Host, Directive, Input } from "@angular/core";
import { NgForOf } from "@angular/common";

@Directive({
    selector: "[ngForTrackByProperty]"
})
export class TrackByPropertyDirective {

    private _propertyName: string = "";

    public constructor(@Host() private readonly _ngFor: NgForOf<any>) {
    }

    @Input("ngForTrackByProperty")
    public set propertyName(value: string | null) {
        // We must accept null in case the user code omitted the ": 'somePropName'" part.
        const strValue: string = value ?? "";

        if (this._propertyName !== strValue) {
            this._ngFor.ngForTrackBy = (_: number, item: any) => strValue ? item[strValue] : item;
            this._propertyName = strValue;
        }
    }

}

Использование :

      <some-tag *ngFor="let item of models; trackByProperty: 'id'">

app.component.html

      <button class="btn btn-warning" (click)="loadCourses()">LoadCourses</button>
<ul>
    <li *ngFor="let course of courses; trackBy:trackCourse">
        {{course.name}}
    </li>
</ul>

app.component.ts

      loadCourses() {

    this.courses = [
    {id:1, name:'cour1'},
    {id:2, name:'cour2'},
    {id:3, name:'cour3'}
    ]  
};

trackCourse(index : number, course: any) {
    return course ? course.id : undefined;
};

Справочный код с Mosh вы можете найти в разделе директив

Целью использования trackBy является установка идентичности элементов в итерируемом объекте. Если Angular увидит два элемента с одинаковым идентификатором, он продолжит проверку содержимого элементов и будет перерисовывать только в случае изменения содержимого. Без идентификации Angular будет полагаться на ссылку на объект элементов, которые обычно меняются, даже если содержимое одинаково, и, таким образом, Angular будет перерисовывать элементы из-за разных ссылок.

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

public function userByName(index: number, user: User): id {
  return user?.id;
}

Несмотря на то, что я видел правильные ответы, как указано выше в этой теме, у меня возникли некоторые проблемы с ворсом/сонаром для неиспользуемого индексного параметра. поэтому я думаю, что приведенный ниже код также может достичь этого действия.

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

public function userByName(user: User): number {
  return user?.id;
}

Еще вы можете использовать

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

а также

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

а также

name="test{{i}}

Это угловой NgFor документ поможет вам https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

Ниже приведен пример для вашего кода

<div *ngFor="let user of users; trackBy:user?.name">{{user.name}} -> {{user.score}}</div>
Другие вопросы по тегам