简体   繁体   English

如何将 `trackBy` 与 `ngFor` 一起使用

[英]How to use `trackBy` with `ngFor`

I can't really understand what I should return from trackBy .我真的不明白我应该从trackBy返回什么。 Based on some examples I've seen on the web, I should return the value of some property on the object.根据我在网上看到的一些示例,我应该返回对象上某些属性的值。 Is it right?这样对吗? Why should I get index as a parameter?为什么要获取index作为参数?

For example, in the following case:例如,在以下情况下:

Component.component.ts组件.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组件.template.html

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

The objects shown in this template are still updated despite the name being unchanged.尽管名称未更改,但此模板中显示的对象仍会更新。 Why?为什么?

On each ngDoCheck triggered for the ngForOf directive, Angular checks what objects have changed.在为ngForOf指令触发的每个ngDoCheck上,Angular 都会检查哪些对象发生了变化。 It uses differs for this process and each differ uses the trackBy function to compare the current object with the new one.它在这个过程中使用了差异,每个差异都使用trackBy函数将当前对象与新对象进行比较。 The default trackBy function tracks items by identity:默认trackBy函数按身份跟踪项目:

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

It receives the current item and should return some value.它接收当前项目并且应该返回一些值。 Then the value returned by the function is compared against the value this function returned the last time.然后将函数返回的值与此函数上次返回的值进行比较。 If the value changes, the differ reports a change.如果值发生变化,diff 报告变化。 So if the default function returns object references, it will not match the current item if the object reference has changed.因此,如果默认函数返回对象引用,则如果对象引用发生更改,它将与当前项不匹配。 So you can provide your custom trackBy function that will return something else.因此,您可以提供将返回其他内容的自定义trackBy函数。 For example, some key value of the object.比如对象的一些键值。 If this key value matches the previous one, then Angular will not detect the change.如果此键值与前一个键值匹配,则 Angular 将不会检测到更改。

The syntax ...trackBy:userByName is no longer supported.语法...trackBy:userByName不再受支持。 You must now provide a function reference.您现在必须提供函数引用。 Here is the basic example:这是基本示例:

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; 
  }

Although the object reference changes, the DOM is not updated.尽管对象引用发生了变化,但 DOM 并未更新。 Here is the plunker.这是笨蛋。 If you're curious how ngFor works under the hood, read this answer .如果您好奇ngFor如何在后台工作, 请阅读此答案

As this topic is still active & finding a clear answer is difficult let me add few examples in addition to @Max 's answer:由于这个话题仍然很活跃,而且很难找到一个明确的答案,所以除了@Max的答案之外,我还要添加几个例子:

app.component.ts 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;
}

Let's display the array into 3 divs using *ngFor .让我们使用*ngForarray显示为 3 个 div。

app.component.html app.component.html

Example of *ngFor without trackBy :没有 trackBy*ngFor示例:

<div *ngFor="let e of array;">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>

What happens if we click the foo button ?如果我们点击foo按钮会发生什么?

→ The 3 divs will be refreshed. → 3 个 div 将被刷新。 Try it yourself, open your console to verify.自己尝试一下,打开你的控制台进行验证。

Example of *ngFor with trackBy :带有 trackBy*ngFor示例:

<div *ngFor="let e of array; trackBy: identify">
   {{e.id}} - {{e.name}}
</div>
<button (click)="foo()">foo</button>

What happens if we click the foo button ?如果我们点击foo按钮会发生什么?

→ Only the first div will be refreshed. → 只有第一个 div 会被刷新。 Try it yourself, open your console to verify.自己尝试一下,打开你的控制台进行验证。

And what if we updated the first object instead of the whole object ?如果我们更新第一个对象而不是整个对象呢?

foo() {
    this.array[0].name = "foo";
}

→ There is no need to use trackBy here. → 这里不需要使用trackBy

It's especially useful when using a Subscription which often looks like what I schematized with array .它在使用通常看起来像我用array模式化的Subscription时特别有用。 So it would look like:所以它看起来像:

 array = [];
 subscription: Subscription;

 ngOnInit(): void {
    this.subscription = this.fooService.getArray().subscribe(data => {
       this.array = data;
    });
 }

 identify(index, item) {
    return item.id;
 }

From the documentation:从文档中:

To avoid this expensive operation, you can customize the default tracking algorithm.为避免这种昂贵的操作,您可以自定义默认跟踪算法。 by supplying the trackBy option to NgForOf.通过向 NgForOf 提供 trackBy 选项。 trackBy takes a function that has two arguments: index and item. trackBy 接受一个有两个参数的函数:index 和 item。 If trackBy is given, Angular tracks changes by the return value of the function.如果给出了 trackBy,Angular 会根据函数的返回值跟踪变化。

Read more here: https://angular.io/api/common/NgForOf在此处阅读更多信息: https ://angular.io/api/common/NgForOf

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

Here's what I use in my projects to allow tracking by a property of the iterated model without the hassle of writing a function in the component's 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 ?? "";
    }

}

Usage :用法 :

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

app.component.html 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 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;
};

Reference Code With Mosh You can find in Directives Section Mosh参考代码您可以在指令部分找到

The purpose of using the trackBy is to set the identity of elements in an iterable.使用 trackBy 的目的是在可迭代对象中设置元素的标识。 If Angular sees two elements with the same identity, it will proceed to check the content of the elements and will only repaint if the content is changed.如果 Angular 看到两个具有相同标识的元素,它将继续检查元素的内容,并且只有在内容发生更改时才会重新绘制。 Without the identity, Angular will rely on the object reference of the elements that usually change even when the content are the same, and thus Angular will repaint the elements because of different references.如果没有标识,Angular 将依赖元素的对象引用,即使内容相同,通常也会发生变化,因此 Angular 会因为引用不同而重新绘制元素。

This angular NgFor document will help you.这个有角度NgFor文档将为您提供帮助。 https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

Below example for your code以下示例为您的代码

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

else you can use否则你可以使用

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

and

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

and

name="test{{i}}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM