Search code examples
htmlangulardropdownhtml-selectonchange

Dropdown(select) in Angular 12 is automatically selecting first item, after manual change in browser


Given following component HTML:

<select (change)="leagueChoosen($event)">
    <option disabled></option>
    <option *ngFor="let league of leagues.entries()" value={{league[1]}}> {{league[0]} </option>
</select>

where leagues are:

leagues:Map<string,number> = new Map([["PORTUGAL 1", 35],["BELGIUM 1", 3], ["GERMANY 2", 18]])

So, the problem is, whenever I select manually an item in my dropdown, the background logic is correct [the function leagueChoosen() ] is doing its job correctly, but, after that automatically first item in the list (the blank one) is selected automatically, and the dropdown text is consequently blank.

UPDATE:(Also I'm new at Angular) There are 3 components: sidebar, content, wrapper(parent of the previous 2)

<!-- sidebar -->
<select (change)="leagueChoosen($event)">
  <option></option>
  <option *ngFor="let league of leagues.entries()" value={{league[1]}}>{{league[0]}}
</option>
</select>

<!-- content -->
<table>
   <caption>Football<app-button (btnClick)="Delete()" text="Delete" float="right"></app-button></caption>
   <thead>
    <th style="text-align: left;">{{data[0]==undefined?"":data[0].liga_header}}</th>
    <th colspan="3">Конечен тип</th>
    <th colspan="3">Голови</th>
</thead>
<tbody>
    <tr *ngFor="let match of data">
        <td>{{match==undefined?"":match.broj}} - {{match==undefined?"":match.datum_vreme.substring(6,match.datum_vreme.length - 2) | date:"HH:mm"}} - {{match==undefined?"":match.tim1}} - {{match==undefined?"":match.tim2}}</td>
        <td>{{match==undefined?"":match.k1}}</td>
        <td>{{match==undefined?"":match.kx}}</td>
        <td>{{match==undefined?"":match.k2}}</td>
        <td>{{match==undefined?"":match.kgg3plus}}</td>
        <td>{{match==undefined?"":match.k0do2}}</td>
        <td>{{match==undefined?"":match.k3plus}}</td>
    </tr>
</tbody>
 <!-- wrapper -->
 <div class='sidebar'>
 <app-sidebar (liClick)="leagueChoosen($event)"></app-sidebar>
 </div>
 <div class='content'>
 <ul>
     <li *ngFor="let league of data"><app-content 
         (dlt)="Delete(league)" 
         [data]="league">
     </app-content>
     </li>
 </ul>
 </div>

Solution

  • The problem is that you're looping over leagues.entries() which re-runs on every template check, which happens after the (change) callback. Therefore the selected DOM element is replaced since entries creates new objects every time. By default ngForuses object reference to check for equality and since the objects are recreated they are not equal and the selected element is recreated.

    One fix is to indeed use [(ngModel)] on the <select> but it does not address the source of the problem.

    To make it cleaner you should use trackBy.

    In your component TS code

    // return the unique league key
    trackLeagues(leagueEntry) { return leagueEntry[1] }
    

    In the template

    *ngFor="let league of leagues.entries(); trackBy: trackLeagues"
    

    this should make sure that the selected <option> element is not replaced.

    Personal hint - I still think it would be best to use an FormControl and subscribe to its valueChanges. To describe how goes well beyond the scope of this answer.