多个角度选择-更改一个值会影响下一个选择

时间:2019-08-10 12:48:08

标签: angular angular-material

在以下情况下,我遇到异常行为。选择项的集合使您可以配置可用汽车列表中的选定汽车列表(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

2 个答案:

答案 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行。

示例stackblitz