Odd issue when ion-selects are created in a *ngFor loop

I’m having an odd issue when I create ion-select components in a *ngFor loop. Say my app lets a user select two favorite colors from a list of available colors. In my *.ts file I have this:

  public colorList: string[] = [ // array of available colors
    'Red',
    'Green',
    'Blue',
    'Pink'
  ]
  public favoriteColors: string[] = [ // array of two favorite colors
    'Red',
    'Green'
  ]

  logFavColors() { // for debug
    console.log(this.favoriteColors);
  }

And in my *.html I have this:

  <ion-list>
    <ion-item *ngFor="let favColor of favoriteColors; let i = index">
      <ion-label>Favorite Color {{ i }}</ion-label>
      <ion-select name="favColor{{ i }}" [(ngModel)]="favoriteColors[i]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
  </ion-list>

So the *ngFor in the <ion-item> creates two ion-selects (one for each favorite color), and the *ngFor in the <ion-select-options> creates the list of colors in the two select boxes. This all works fine initially. If I change the first favorite color from red to green, the backing data gets updated and the GUI shows “green” for the first favorite color. All good. However, if I then change the first favorite color back to red, BOTH the first AND second favorite colors get switched to “red” in the GUI. Strangely, the backing data is correct (first favorite == “red” and second favorite == “green”), it’s just the GUI that is incorrect.

If I change the code so each ion-select is created independently (without the outer *ngFor) like so:

  <ion-list>
    <ion-item>
      <ion-label>Favorite Color 0b</ion-label>
      <ion-select name="favColor0b" [(ngModel)]="favoriteColors[0]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
    <ion-item>
      <ion-label>Favorite Color 1b</ion-label>
      <ion-select name="favColor1b" [(ngModel)]="favoriteColors[1]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
  </ion-list>

Then everything works fine. The GUI and backing data get updated correctly.

I’m using the latest Ionic:

   Ionic CLI                     : 6.19.0 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 6.1.2
   @angular-devkit/build-angular : 13.2.6
   @angular-devkit/schematics    : 13.2.6
   @angular/cli                  : 13.2.6
   @ionic/angular-toolkit.       : 6.1.0

Any ideas?

Thanks

This works for you?

 <ion-list>
    <ion-item *ngFor="let favColor of favoriteColors; let i = index">
      <ion-label>Favorite Color {{ i }}</ion-label>
      <ion-select (ionChange)="logFavColors()" [(ngModel)]="favoriteColors[i]">
        <ion-select-option *ngFor="let color of colorList">{{
          color
        }}</ion-select-option>
      </ion-select>
    </ion-item>
  </ion-list>

This in your code is undefined: name="favColor{{ i }}". What’s your intention with this?

Your stackblitz shows the same problem. Removing the name property in the ion-select did not fix it. Try changing the first favorite color to green, then back to red. You’ll see the second favorite color changes to red too.

Setting the name property to “favColor{{ i }}” simply assigns a unique name to each ion-select component (“favColor0” and “favColor1”). This is allowed according to the ion-select API.

Correct - and I forget to add [value]=‘favColor’ so a value is assigned after selection

Bit weird - as the drop down only shows one item.

While if I implement the code in a starter app, the full list shows, and the buggy behavior sometimes shows and sometimes doesn’t…

And if I remote the ngModel and let the ionChange handle matters, then both selects seem to influence each other as well (see stackblitz)

After a little more digging I have this to share.
As mentioned above, I created two sets of code.
This code does NOT work:

  <ion-list>
    <ion-item *ngFor="let favColor of favoriteColors; let i = index">
      <ion-label>Favorite Color {{ i }}</ion-label>
      <ion-select name="favColor{{ i }}" [(ngModel)]="favoriteColors[i]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
  </ion-list>

And this code does work:

  <ion-list>
    <ion-item>
      <ion-label>Favorite Color 0b</ion-label>
      <ion-select name="favColor0b" [(ngModel)]="favoriteColors[0]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
    <ion-item>
      <ion-label>Favorite Color 1b</ion-label>
      <ion-select name="favColor1b" [(ngModel)]="favoriteColors[1]" (ionChange)="logFavColors()">
        <ion-select-option *ngFor="let color of colorList">{{ color }}</ion-select-option>
      </ion-select>
    </ion-item>
  </ion-list>

Inspecting the DOM in Chrome I see that the only difference between these two sets of code (other then “id” attributes) is that the non-working code does not include a “name” attribute in the ion-select element. Conversely, in the working code a name attribute is created which matches the name attribute of the component’s corresponding <input type=“hidden”> element.

For the non-working code, I can manually add a “name” attribute to the ion-select DOM element. However, if I interact with the ion-select component in the GUI, then the “name” attribute is removed and the code still does not work.

Curiouser and curiouser.

U reckon you are onto something big??? :sweat_smile::sweat_smile::sweat_smile::stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes:

Just finish this up, I thought I’d share my solution. To clarify, this issue with ion-select isn’t really an Ionic issue. The same odd behavior happens with plain, vanilla Angular select widgets too.

My solution was to switch from Angular template-driven forms to reactive forms. I like the ease-of-use of template-driven forms, but in this case they just don’t work properly. So here’s my reactive form solution:

  constructor(private fb: FormBuilder) { }

  public colorList: string[] = [ // array of available colors
    'Red',
    'Green',
    'Blue',
    'Pink'
  ]

  public favColorForm: FormArray = this.fb.array([ // FormArray of two favorite colors
    this.fb.group({ color: ['Red'] }),
    this.fb.group({ color: ['Green'] })
  ]);

  logFavColors() { // for debug
    console.log(JSON.stringify(this.favColorForm.value));
  }
  <ion-list>
    <ion-item-group *ngFor="let favColorGroup of favColorForm.controls; let i = index">
      <ion-item [formGroup]="favColorGroup">
        <ion-label>Favorite Color {{ i }}</ion-label>
        <ion-select formControlName="color" (ionChange)="logFavColors()">
          <ion-select-option *ngFor="let c of colorList">{{ c }}</ion-select-option>
        </ion-select>
      </ion-item>
    </ion-item-group>
  </ion-list>

Works like a champ.

1 Like