Selected item does not change

Viewed 918

Working with a newly created Angular 11 project.

app.component.html

<div *ngFor="let i of getItems()">
  <select (change)="onSelectionChange($event.target.value)">
    <option></option>
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
  </select>
</div>

app.component.ts

export class AppComponent {
  
  getItems(): any[] {
    return [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
  }

  onSelectionChange(event) {
    console.log(event);
  }
}

Everything seems to be ok. Four select boxes are visible each one with an empty value and four integers in the dropdown list. The strange thing is that when I open a dropdown and click to select a value, the onSelectionChanged() event gets triggered but the selected value does not change in the select.

The select components start working as expected if I change the getItems() method to return plain numbers instead of objects, as follows:

getItems(): any[] {
  return [1, 2, 3, 4];
}

Moreover, I get the correct behaviour also removing the (change) event from the HTML markup (in this case the event won't be triggered).

I'm working with these dependencies

"dependencies": {
  "@angular/animations": "~11.0.1",
  "@angular/common": "~11.0.1",
  "@angular/compiler": "~11.0.1",
  "@angular/core": "~11.0.1",
  "@angular/forms": "~11.0.1",
  "@angular/platform-browser": "~11.0.1",
  "@angular/platform-browser-dynamic": "~11.0.1",
  "@angular/router": "~11.0.1",
  "rxjs": "~6.6.0",
  "tslib": "^2.0.0",
  "zone.js": "~0.10.2"
},
"devDependencies": {
  "@angular-devkit/build-angular": "~0.1100.2",
  "@angular/cli": "~11.0.2",
  "@angular/compiler-cli": "~11.0.1",
  "@types/jasmine": "~3.6.0",
  "@types/node": "^12.11.1",
  "codelyzer": "^6.0.0",
  "jasmine-core": "~3.6.0",
  "jasmine-spec-reporter": "~5.0.0",
  "karma": "~5.1.0",
  "karma-chrome-launcher": "~3.1.0",
  "karma-coverage": "~2.0.3",
  "karma-jasmine": "~4.0.0",
  "karma-jasmine-html-reporter": "^1.5.0",
  "protractor": "~7.0.0",
  "ts-node": "~8.3.0",
  "tslint": "~6.1.0",
  "typescript": "~4.0.2"
}
4 Answers

Use trackBy to resolve the issue. Each time change detection is triggered, angular re-renders the entire template and i gets evaluated to the first object always.

.html

<div>
    <select (change)="onSelectionChange ($event.target)">
       <option *ngFor="let i of getItems(); trackBy:identify"
       value="{{i.value}}">{{i.label}}</option>
    </select>
</div>

.ts

identify(index, item) {
   return item.label;
}

You have not specified any value for the selected option. I fyou debug and watch the value of $event.target.value i think it'll be empty.

Try to specify the value of the option with the attribute. Example from here:

<mat-form-field appearance="fill">
 <mat-label>Cars</mat-label>
 <select matNativeControl required>
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="mercedes">Mercedes</option>
    <option value="audi">Audi</option>
  </select>
</mat-form-field>

We're calling a method to get the items and every time we call it it's returning a new array of objects. Since it's a new object we don't know if it's the same value or a different one. In the cause of primitive value array, it wasn't the case that's why it was working. We can handle this with a trackBy function to tell the ngFor how to identify it's items as below.

trackByItem = (index: number, item: any) => item.a;
<div *ngFor="let i of getItems();trackBy:trackByItem">
...
</div>

it's not related to your angular version or dependencies, your options and select value should be equal to show value in select menu, now your values are object and your options are number

Related