3

I'm trying to use *ngFor to build a bundle of multiple <select> (3x of them) and I found this other SO to get me going. However, the problem I have now is that they are not independent, if I change the first select, it also reflects the change on the second select (but not the third one??). One thing worth to know is that all Select have the same list, which is why the first one affects second one, but I really want them independent.

Oh I'm currently using Angular 5, so it's not the latest.

Here's the code I have to loop through the 3 Select with *ngFor

<div class="col-sm-3" *ngFor="let groupField of selectedGroupingFields; let i = index;">
<select class="form-control col-sm-6" name="groupField{{i}}" [(ngModel)]="selectedGroupingFields[i]" (ngModelChange)="groupByFieldName($event, i)">
    <option value=""></option>
    <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}}</option>
</select>

With the collection being structure like this

selectedGroupingFields: string[] = ['', '', ''];    
columnDefinitions = [
  { id: 'title', name: 'Title'}, 
  { id: 'duration', name: 'Duration'},
  { id: 'percentComplete', name: '% Complete'},
  { id: 'start', name: 'Start'},
  { id: 'Finish', name: 'Finish'},
  { id: 'cost', name: 'Cost'},
  { id: 'effortDriven', name: 'Effort-Driven'}
];

To demo the problem, I made this little function that is triggered by a button click

changeFirstGroupBy() {
  this.selectedGroupingFields[0] = 'title';
}

When I click my button and execute that function, it changes the first 2 Select... why?

So I did more tests and decided to create each Select separately by recopying them 1 by 1 and just remove the *ngFor on the <div> like this

<div class="col-sm-3">
    <select class="form-control col-sm-6" name="groupField1" [(ngModel)]="selectedGroupingFields[0]" (ngModelChange)="groupByFieldName($event, i)">
        <option value=""></option>
        <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}}</option>
    </select>
</div>
<div class="col-sm-3">
    <select class="form-control col-sm-6" name="groupField2" [(ngModel)]="selectedGroupingFields[1]" (ngModelChange)="groupByFieldName($event, i)">
        <option value=""></option>
        <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}}</option>
    </select>
</div>
<div class="col-sm-3">
    <select class="form-control col-sm-6" name="groupField3" [(ngModel)]="selectedGroupingFields[2]" (ngModelChange)="groupByFieldName($event, i)">
        <option value=""></option>
        <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}}</option>
    </select>
</div>

and that works! but why? That is supposed to be the exact same code as the *ngFor. Also if I change the last select (see in demo), it affects the second select too, that is so strange but it's really working correctly when I create all 3 Select manually.

Here's an animated GIF of the problem

enter image description here

EDIT

Forgot to mention, I also tried to change 2 way binding to 1 way and it has the same effect [ngModel]="selectedGroupingFields[i]"

ANSWERED

This is now working and used for an Open Source library of mine Angular-Slickgrid and here's the demo of where I use this answer. Thanks :)

ghiscoding
  • 12,308
  • 6
  • 69
  • 112

2 Answers2

1

Hey you are using two way data binding

[(ngModel)]=selectedGroupingFields[i]

To make it work as you wish you can make it as following:

<div class="col-sm-3" *ngFor="let groupField of selectedGroupingFields; let i = index;">
 <select class="form-control col-sm-6" name="groupField{{i}}" [ngModel]="selectedGroupingFields[i]" (ngModelChange)="groupByFieldName($event, i)">
  <option value=""></option>
  <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}} 
 </option>
</select>

Please check working example here: Link

jakubm
  • 412
  • 4
  • 11
  • Oh I forgot to mention that I tried with `[ngModel]` only and that still behave the same. I tried your code and I still see the problem :( – ghiscoding Dec 07 '18 at 15:39
  • I edited my answer and added working example on stackblitz. Please check if that's working for you. – jakubm Dec 07 '18 at 15:42
  • it's unfortunate that even after copying your code it still didn't work, however @ConnorsFan answer does work. However your blitz code does work, so I'll up vote anyway. Thanks – ghiscoding Dec 07 '18 at 20:14
  • When I tried the stackblitz, I had some strange behavior when clicking on the button. For example: (1) Select a value in the first list, (2) Click on the button, (3) See that the value of the second list changed. – ConnorsFan Dec 07 '18 at 20:26
1

You can solve this problem by providing a trackBy function to the ngFor directive:

<div ... *ngFor="let groupField of selectedGroupingFields; let i = index; trackBy: trackByFn">
  <select ... name="groupField{{i}}" [(ngModel)]="selectedGroupingFields[i]" (ngModelChange)="groupByFieldName($event, i)">
    <option value=""></option>
    <option [ngValue]="field.id" *ngFor="let field of columnDefinitions">{{field.name}}</option>
  </select>
</div>

where trackByFn returns the item index:

trackByFn(index, item) {
  return index;
}

See this stackblitz for a demo.

ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • ahh yes this is working, great. I don't use the TrackBy very often but is there a way to put that code all in the view (without creating ViewModel code)? – ghiscoding Dec 07 '18 at 20:12
  • I don't think so. You cannot create a function in the HTML template. – ConnorsFan Dec 07 '18 at 20:15
  • 1
    Yeah it doesn't look like, I tried it and that didn't work. Thanks a lot for the working solution, this is for an Open Source lib [Angular-Slickgrid](https://github.com/ghiscoding/Angular-Slickgrid) and here's the [demo](https://ghiscoding.github.io/Angular-Slickgrid/#/draggrouping)... so you helped me helped others :) Cheers – ghiscoding Dec 07 '18 at 20:55