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