122

我真的不明白我应该从什么返回trackBy。根据我在网上看到的一些示例,我应该返回对象上某些属性的值。这样对吗?为什么我应该得到index一个参数?

例如,在以下情况下:

组件.component.ts

constructor() {
    window.setInterval(() => this.users = [
            { name: 'user1', score: Math.random() },
            { name: 'user2', score: Math.random() }
        ],
        1000);
}

userByName(index, user) {
    return user.name;
}

组件.template.html

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

尽管名称未更改,但此模板中显示的对象仍会更新。为什么?

4

5 回答 5

158

每次ngDoCheck触发ngForOf指令时,Angular 都会检查哪些对象发生了变化。它为此过程使用了不同的方法,并且每个不同的方法都使用该 trackBy函数将当前对象与新对象进行比较。默认trackBy函数按身份跟踪项目:

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

它接收当前项目并且应该返回一些值。然后将函数返回的值与此函数上次返回的值进行比较。如果值发生变化,diff 报告变化。因此,如果默认函数返回对象引用,则如果对象引用发生更改,它将与当前项不匹配。因此,您可以提供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对引擎盖下的工作原理感到好奇,请阅读此答案

于 2017-07-29T15:58:55.587 回答
49

由于这个话题仍然很活跃,而且很难找到一个明确的答案,所以除了@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 个 div显示*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;
 }

从文档中:

为了避免这种昂贵的操作,您可以自定义默认的跟踪算法。通过向 NgForOf 提供 trackBy 选项。trackBy 接受一个有两个参数的函数:index 和 item。如果给出了 trackBy,Angular 会根据函数的返回值跟踪变化。

在这里阅读更多:https ://angular.io/api/common/NgForOf

在这里找到我的原始答案:https ://stackoverflow.com/a/57890227/9753985

于 2019-09-20T09:46:42.903 回答
6

这是我在我的项目中使用的,以允许通过迭代模型的属性进行跟踪,而无需在组件的类中编写函数:

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'">

于 2021-07-31T00:23:18.110 回答
-9

这个角度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>
于 2017-02-08T09:21:35.173 回答
-14

否则你可以使用

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

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

name="test{{i}}
于 2019-12-06T05:40:36.530 回答