当元素从有界数组拼接时,ngFor 的行为与 trackBy index 和 trackBy Id 不同
Behaviour of ngFor differs with trackBy index and trackBy Id when element is spliced from bounded array
我有一小部分用户。现在,我为每个数组元素添加了一个独立的组件(用户选择),它没有来自父组件的数据绑定并显示自己的数据。
当我删除任何索引处的用户并使用 trackBy 索引时,最后一个用户选择元素将被删除,而不是删除索引处的元素。
但是,当我使用 trackBy id(唯一标识符)时,它会按预期工作。
我无法理解 trackBy 在这两种情况下有何不同。
这是相同的演示 - https://stackblitz.com/edit/angular-ivy-u4pwrl
app.component.html
<h1>By Id</h1>
<div *ngFor="let user of users1; let idx = index; trackBy: trackById">
<app-user-choice></app-user-choice>
<button (click)="deleteUser1(idx)">Delete</button>
</div>
<h1>By index</h1>
<div *ngFor="let user of users2; let idx = index; trackBy: trackByIndex">
<app-user-choice></app-user-choice>
<button (click)="deleteUser2(idx)">Delete</button>
</div>
app.component.ts
export class AppComponent {
name = 'Angular ' + VERSION.major;
users1 = [
{
name: 'a',
id: 1
},
{
name: 'b',
id: 2
},
{
name: 'c',
id: 3
},
{
name: 'd',
id: 4
},
{
name: 'e',
id: 5
}
];
users2 = [
{
name: 'a',
id: 1
},
{
name: 'b',
id: 2
},
{
name: 'c',
id: 3
},
{
name: 'd',
id: 4
},
{
name: 'e',
id: 5
}
];
num = 0;
deleteUser1(idx) {
this.users1.splice(idx, 1);
}
deleteUser2(idx) {
this.users2.splice(idx, 1);
}
trackById(index, item) {
return item.id;
}
trackByIndex(index) {
return index;
}
}
用户-choice.component.html
<p>
user-info works! {{index}}
</p>
用户-choice.component.ts
export class UserChoiceComponent implements OnInit {
index = Math.random() * 10;
constructor() {}
ngOnInit() {}
}
trackBy
应该防止 Angular 在预期只有一个更改时跟踪可迭代对象中的所有更改。
主要问题是,您的 (click)
指令在 track by 发挥作用之前被触发。您单击删除按钮,项目将被删除,然后 trackBy
检查更改。
使用trackById()
-方法,您始终return 已删除对象的实际id
。例如,您单击具有 id
3 和 trackById
returns 3 的对象。因此 Angular 可以检测到已更改的对象并将其从 UI 中正确删除.
trackByIndex()
returns Angular 自己的循环索引。现在想象你有一个对象列表,其中 id
s 从 0 到 4。只有在开始时 Angular 的索引和对象的 id 匹配。现在你删除了一个对象。比方说id
3的对象,那么对象之间就没有了id
3,但是Angular仍然监视着index
3的对象。而且,对象索引 0 到 2 的 ID 为 0 到 2,但索引 3 的对象的 ID 为 4,因为索引总是根据条目的顺序进行调整。
Angular 不知道哪个对象实际发生了变化,因为它监视了错误的项目。因此使用默认行为并从列表中删除最后一个条目。这就是 byIndex
方法在这里根本不起作用的原因。
一个例子
开始
name: a
Angular id: 0
object id: 0
name: b
Angular id: 1
object id: 1
name: c
Angular id: 2
object id: 2
name: d
Angular id: 3
object id: 3
name: e
Angular id: 4
object id: 4
现在您在 ID 为 3 的对象上单击 delete
。
当你这样做的时候。该项目首先从列表中删除,然后 trackBy
才开始工作。但是 id
3 不再有对象。
name: a
Angular id: 0
object id: 0
name: b
Angular id: 1
object id: 1
name: c
Angular id: 2
object id: 2
name: e
Angular id: 3
object id: 4
你看。主要问题是,使用 Angular 的静态有序索引在这里不起作用,因为对象的索引永远不会匹配。您必须使用对象自己的 ID。
我有一小部分用户。现在,我为每个数组元素添加了一个独立的组件(用户选择),它没有来自父组件的数据绑定并显示自己的数据。
当我删除任何索引处的用户并使用 trackBy 索引时,最后一个用户选择元素将被删除,而不是删除索引处的元素。
但是,当我使用 trackBy id(唯一标识符)时,它会按预期工作。
我无法理解 trackBy 在这两种情况下有何不同。
这是相同的演示 - https://stackblitz.com/edit/angular-ivy-u4pwrl
app.component.html
<h1>By Id</h1>
<div *ngFor="let user of users1; let idx = index; trackBy: trackById">
<app-user-choice></app-user-choice>
<button (click)="deleteUser1(idx)">Delete</button>
</div>
<h1>By index</h1>
<div *ngFor="let user of users2; let idx = index; trackBy: trackByIndex">
<app-user-choice></app-user-choice>
<button (click)="deleteUser2(idx)">Delete</button>
</div>
app.component.ts
export class AppComponent {
name = 'Angular ' + VERSION.major;
users1 = [
{
name: 'a',
id: 1
},
{
name: 'b',
id: 2
},
{
name: 'c',
id: 3
},
{
name: 'd',
id: 4
},
{
name: 'e',
id: 5
}
];
users2 = [
{
name: 'a',
id: 1
},
{
name: 'b',
id: 2
},
{
name: 'c',
id: 3
},
{
name: 'd',
id: 4
},
{
name: 'e',
id: 5
}
];
num = 0;
deleteUser1(idx) {
this.users1.splice(idx, 1);
}
deleteUser2(idx) {
this.users2.splice(idx, 1);
}
trackById(index, item) {
return item.id;
}
trackByIndex(index) {
return index;
}
}
用户-choice.component.html
<p>
user-info works! {{index}}
</p>
用户-choice.component.ts
export class UserChoiceComponent implements OnInit {
index = Math.random() * 10;
constructor() {}
ngOnInit() {}
}
trackBy
应该防止 Angular 在预期只有一个更改时跟踪可迭代对象中的所有更改。
主要问题是,您的 (click)
指令在 track by 发挥作用之前被触发。您单击删除按钮,项目将被删除,然后 trackBy
检查更改。
使用trackById()
-方法,您始终return 已删除对象的实际id
。例如,您单击具有 id
3 和 trackById
returns 3 的对象。因此 Angular 可以检测到已更改的对象并将其从 UI 中正确删除.
trackByIndex()
returns Angular 自己的循环索引。现在想象你有一个对象列表,其中 id
s 从 0 到 4。只有在开始时 Angular 的索引和对象的 id 匹配。现在你删除了一个对象。比方说id
3的对象,那么对象之间就没有了id
3,但是Angular仍然监视着index
3的对象。而且,对象索引 0 到 2 的 ID 为 0 到 2,但索引 3 的对象的 ID 为 4,因为索引总是根据条目的顺序进行调整。
Angular 不知道哪个对象实际发生了变化,因为它监视了错误的项目。因此使用默认行为并从列表中删除最后一个条目。这就是 byIndex
方法在这里根本不起作用的原因。
一个例子
开始
name: a
Angular id: 0
object id: 0
name: b
Angular id: 1
object id: 1
name: c
Angular id: 2
object id: 2
name: d
Angular id: 3
object id: 3
name: e
Angular id: 4
object id: 4
现在您在 ID 为 3 的对象上单击 delete
。
当你这样做的时候。该项目首先从列表中删除,然后 trackBy
才开始工作。但是 id
3 不再有对象。
name: a
Angular id: 0
object id: 0
name: b
Angular id: 1
object id: 1
name: c
Angular id: 2
object id: 2
name: e
Angular id: 3
object id: 4
你看。主要问题是,使用 Angular 的静态有序索引在这里不起作用,因为对象的索引永远不会匹配。您必须使用对象自己的 ID。