如何使用 `trackBy` 和 `ngFor`
How to use `trackBy` with `ngFor`
我真的无法从 trackBy
中理解我应该 return 什么。根据我在网上看到的一些示例,我应该 return 对象上某些 属性 的值。这样对吗?为什么要获取 index
作为参数?
例如,在以下情况下:
Component.component.ts
constructor() {
window.setInterval(() => this.users = [
{ name: 'user1', score: Math.random() },
{ name: 'user2', score: Math.random() }
],
1000);
}
userByName(index, user) {
return user.name;
}
Component.template.html
<div *ngFor="let user of users; trackBy:userByName">
{{user.name}} -> {{user.score}}
</div>
尽管名称未更改,但此模板中显示的对象仍在更新。为什么?
这份 angular 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>
在为 ngForOf
指令触发的每个 ngDoCheck
上,Angular 检查哪些对象已更改。它在这个过程中使用 differ,每个 diff 使用 trackBy
函数将当前对象与新对象进行比较。默认 trackBy
函数按标识跟踪项目:
const trackByIdentity = (index: number, item: any) => item;
它接收当前项目并且应该 return 一些值。然后将函数 returned 的值与上次此函数 returned 的值进行比较。如果值发生变化,differ 会报告变化。所以如果默认函数 returns 对象引用,如果对象引用发生变化,它将不匹配当前项。因此,您可以提供您的自定义 trackBy
函数,该函数将 return 其他内容。例如,对象的一些键值。如果此键值与前一个键值匹配,则 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 没有更新。 Here is the plunker. If you're curious how ngFor
works under the hood, .
由于这个话题仍然活跃并且很难找到明确的答案让我在 @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;
}
让我们使用 *ngFor
.
将 array
显示为 3 div 秒
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
with 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;
}
来自文档:
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.
在此处阅读更多内容:https://angular.io/api/common/NgForOf
在这里找到我原来的答案:
否则你可以使用
*ngFor="a of array; index as i;"
和
[attr.data-target]="'#test' + i"
和
name="test{{i}}
这是我在我的项目中使用的方法,允许通过迭代模型的 属性 进行跟踪,而无需在组件的 class 中编写函数的麻烦:
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>) {
this._ngFor.ngForTrackBy = (_: number, item: any) => this._propertyName ? item[this._propertyName] : item;
}
@Input("ngForTrackByProperty")
public set propertyName(value: string | null) {
// We must accept null in case the user code omitted the ": 'somePropName'" part.
this._propertyName = value ?? "";
}
}
用法:
<some-tag *ngFor="let item of models; trackByProperty: 'yourDiscriminantProp'">
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 编写代码
You can find in Directives Section
我真的无法从 trackBy
中理解我应该 return 什么。根据我在网上看到的一些示例,我应该 return 对象上某些 属性 的值。这样对吗?为什么要获取 index
作为参数?
例如,在以下情况下:
Component.component.ts
constructor() {
window.setInterval(() => this.users = [
{ name: 'user1', score: Math.random() },
{ name: 'user2', score: Math.random() }
],
1000);
}
userByName(index, user) {
return user.name;
}
Component.template.html
<div *ngFor="let user of users; trackBy:userByName">
{{user.name}} -> {{user.score}}
</div>
尽管名称未更改,但此模板中显示的对象仍在更新。为什么?
这份 angular 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>
在为 ngForOf
指令触发的每个 ngDoCheck
上,Angular 检查哪些对象已更改。它在这个过程中使用 differ,每个 diff 使用 trackBy
函数将当前对象与新对象进行比较。默认 trackBy
函数按标识跟踪项目:
const trackByIdentity = (index: number, item: any) => item;
它接收当前项目并且应该 return 一些值。然后将函数 returned 的值与上次此函数 returned 的值进行比较。如果值发生变化,differ 会报告变化。所以如果默认函数 returns 对象引用,如果对象引用发生变化,它将不匹配当前项。因此,您可以提供您的自定义 trackBy
函数,该函数将 return 其他内容。例如,对象的一些键值。如果此键值与前一个键值匹配,则 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 没有更新。 Here is the plunker. If you're curious how ngFor
works under the hood,
由于这个话题仍然活跃并且很难找到明确的答案让我在 @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;
}
让我们使用 *ngFor
.
array
显示为 3 div 秒
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
with 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;
}
来自文档:
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.
在此处阅读更多内容:https://angular.io/api/common/NgForOf
在这里找到我原来的答案:
否则你可以使用
*ngFor="a of array; index as i;"
和
[attr.data-target]="'#test' + i"
和
name="test{{i}}
这是我在我的项目中使用的方法,允许通过迭代模型的 属性 进行跟踪,而无需在组件的 class 中编写函数的麻烦:
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>) {
this._ngFor.ngForTrackBy = (_: number, item: any) => this._propertyName ? item[this._propertyName] : item;
}
@Input("ngForTrackByProperty")
public set propertyName(value: string | null) {
// We must accept null in case the user code omitted the ": 'somePropName'" part.
this._propertyName = value ?? "";
}
}
用法:
<some-tag *ngFor="let item of models; trackByProperty: 'yourDiscriminantProp'">
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 编写代码 You can find in Directives Section