在以下情况下,我遇到异常行为。选择项的集合使您可以配置可用汽车列表中的选定汽车列表(myCars
。
<div *ngFor="let car of myCars; let i = index;">
<mat-form-field>
<mat-label>Car</mat-label>
<mat-select [(value)]="myCars[i]" (selectionChange)="selectionChanged($event, i)">
<mat-option>-</mat-option>
<mat-option *ngFor="let car of carsAvailable" [value]="car">{{car.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<button mat-raised-button color="primary" type="button" (click)="addNewCar()">Add new car</button>
问题是,例如,当我在列表中有两辆车时:
Select1 : Ferrari
Select2 : Ferrari
并将Select1
的值更改为Audi
,两个选择都被错误地设置为该值:
Select1 : Audi
Select2 : Audi
但是myCars数组包含[Ferrari, Audi]
,这是正确的。
有趣的是,如果我改为在select2
中选择Audi,那么select1
将保持不变。而且即使我选择了10个相同的选择并选择了法拉利并将第i个选择更改为Audi,也只有第i + 1个选择也会更改为Audi。
您有没有人解释为什么会发生这种情况?在我看来,这是一种竞争状况,其中第i个选择对应的DOM项首先被删除,而Angular设置第i + 1个选择,因为它通过对象引用与之匹配。
这是问题的一个示例: https://stackblitz.com/edit/angular-74wwjj?file=app%2Fselect-value-binding-example.ts
答案 0 :(得分:3)
当前行为的原因:
最初考虑您具有以下列表:
1. Ferrari
2. Ferrari
3. Ferrari
在这里,仅出于说明目的提到了1,2,3个索引。实际上,它们是相同的对象。
现在,当您将第一个下拉菜单更改为Audi
时,新列表将变为:
Audi
1. Ferrari
2. Ferrari
下表说明了为什么会变成这样而不是下面。
Audi
2. Ferrari
3. Ferrari
内部角度维护项目的链接列表。因此,在下表中提到了“新列表项”的“下一个”和“上一个”链接。要了解该表,请逐行阅读。尽管在实际代码中,链表更改和DOM更新是分开的,但为了简单起见,我在下面的说明中将它们组合在一起。
Old List | New List | New List prev | New List next | Description
1.Ferrari | Audi | null | 1.Ferrari | As Audi is new object, it will create new DOM node for this item and attaches it at 0 index. It will detach the 1.Ferrari object from index: 0.
2.Ferrari | 1.Ferrari | Audi | 2.Ferrari | It first checks if Ferrari object exists in detached list. In this case it does exist. So, it will re-attach the detached 1.Ferrari object at index: 1
3.Ferrari | 2.Ferrari | 1.Ferrari | 3.Ferrari | It checks if Ferrari exists in detached list. In this case it doesn't. So, it will attach the 2.Ferrari at index: 2.
现在,列表(2.Ferrari)中最后一项的下一项是3.Ferrari。由于新列表的长度应为3,因此它将截断列表并丢弃3.Ferrari。
因此,如果再次检查共享的演示,您会发现,当我们更改第一个下拉菜单的值时,感觉就像焦点转移到了第二个项目上。之所以这样,是因为实际上它只是将我们的第一项DOM移至第二位置。由于它只是移动记录而未对该项目进行任何更改检测,因此第二个下拉列表下方的显示值仍显示法拉利。
解决方案:
您可以通过设置trackBy
函数来解决此问题。因此,它可以按trackBy
函数的返回值进行跟踪,而不是按对象引用跟踪项。在下面的示例中,它正在按索引跟踪项目。
<div *ngFor="let car of myCars; let i = index; trackBy: trackByIndex">
</div>
trackByIndex(index, item) {
return index;
}
工作示例:https://stackblitz.com/edit/angular-74wwjj-olzks2
希望这会有所帮助!
答案 1 :(得分:1)
您必须像
那样更改HTML<div *ngFor="let car of myCars; let i = index;">
<mat-form-field>
<mat-label>Car</mat-label>
<mat-select [(value)]="car" (selectionChange)="selected($event, i)">
<mat-option>-</mat-option>
<mat-option *ngFor="let _car of carsAvailable" [value]="_car">{{_car.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
因为,您在DOM中有两个名称分别为car的变量。因此,您必须更改它。并且您还必须将mat-select
的value属性更改为car。
最后,从ts文件的this.myCars[index] = matSelectChange.value;
函数中删除selected
行。