如何使用 `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会被刷新。 亲自尝试,打开控制台进行验证。

*ngForwith 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