Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Angular selects - changing one value affect the next select

I'm experiencing a strange behaviour in the following scenario. A colleciton of selects allows you to configure a list of cars selected(myCars) from the list of cars available.

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

The problem is that when I have two cars in my list, for instance:

Select1 : Ferrari
Select2 : Ferrari

and change the value of the Select1 to Audi, both selects are incorrectly set to that value:

Select1 : Audi
Select2 : Audi

however myCars array contains [Ferrari, Audi], which is correct. Interestingly if I pick Audi in select2 instead, select1 would remain unchanged. And even if I had 10 identical selects with Ferrari selected and changed i-th select to Audi, only the i+1th would change to Audi as well.

Does any of you have an explanation of why this might be happening? To me it looks like some sort of a race condition, where the DOM item corresponding to the i-th select gets removed first and Angular sets the i+1th select because it matches it by the object reference.

Here's an example of the issue: https://stackblitz.com/edit/angular-74wwjj?file=app%2Fselect-value-binding-example.ts

like image 879
user4205580 Avatar asked Jun 13 '26 15:06

user4205580


1 Answers

Reason for current behaviour:

Consider initially you have list as:

1. Ferrari
2. Ferrari
3. Ferrari

Here, 1,2,3 indexes are mentioned just for explanation purpose. Actually they are the same objects.

Now, when you change the first dropdown to Audi, the new list becomes:

Audi
1. Ferrari
2. Ferrari

Why it is becoming like this instead of below is explained in below table.

Audi
2. Ferrari
3. Ferrari

Internally angular maintains a linked list for items. So in below table New List Items' Next and Prev links are mentioned. To understand the table, read it row by row. Though in actual code the linked list changes and DOM updates are happeing separately, to keep it simple, I have combined them in below explanation.

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.

Now, Next item to last item in the list (2.Ferrari) is 3.Ferrari. As length of the new list should be 3, it will truncate the list and discard the 3.Ferrari.

So, if you again check the demo you shared, you can see that it feels like focus is shifted to second item when we change value of first dropdown. It feels like so because it is actually just moving our first item DOM to second position. As it is just moving that record and not doing any change detection for that item, the display value below the second dropdown still shows Ferrari.

Solution:

You can solve this by setting trackBy function. So, instead of tracking items by object references, it can track by return value of the trackBy function. In below exmaple, it is tracking item by index.

<div *ngFor="let car of myCars; let i = index; trackBy: trackByIndex">
</div>
trackByIndex(index, item) {
 return index;
}

Working example: https://stackblitz.com/edit/angular-74wwjj-olzks2

Hope it will help!

like image 106
Shripal Soni Avatar answered Jun 16 '26 09:06

Shripal Soni



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!