536

I'd like to bind a select element to a list of objects -- which is easy enough:

@Component({
   selector: 'myApp',
   template: 
      `<h1>My Application</h1>
       <select [(ngModel)]="selectedValue">
          <option *ngFor="#c of countries" value="c.id">{{c.name}}</option>
       </select>`
    })
export class AppComponent{
   countries = [
      {id: 1, name: "United States"},
      {id: 2, name: "Australia"}
      {id: 3, name: "Canada"},
      {id: 4, name: "Brazil"},
      {id: 5, name: "England"}
   ];
   selectedValue = null;
}

In this case, it appears that selectedValue would be a number -- the id of the selected item.

However, I'd actually like to bind to the country object itself so that selectedValue is the object rather than just the id. I tried changing the value of the option like so:

<option *ngFor="#c of countries" value="c">{{c.name}}</option>

but this does not seem to work. It seems to place an object in my selectedValue -- but not the object that I'm expecting. You can see this in my Plunker example.

I also tried binding to the change event so that I could set the object myself based on the selected id; however, it appears that the change event fires before the bound ngModel is updated -- meaning I don't have access to the newly selected value at that point.

Is there a clean way to bind a select element to an object with Angular 2?

Abd Elbeltaji
  • 473
  • 2
  • 13
RHarris
  • 10,641
  • 13
  • 59
  • 103
  • Just realized my Plunk works a little differently in IE vs. Chrome. Neither one actually works the way I'm wanting, but FYI. – RHarris Mar 11 '16 at 16:22

16 Answers16

940
<h1>My Application</h1>
<select [(ngModel)]="selectedValue">
  <option *ngFor="let c of countries" [ngValue]="c">{{c.name}}</option>
</select>

StackBlitz example

NOTE: you can use [ngValue]="c" instead of [ngValue]="c.id" where c is the complete country object.

[value]="..." only supports string values
[ngValue]="..." supports any type

update

If the value is an object, the preselected instance needs to be identical with one of the values.

See also the recently added custom comparison https://github.com/angular/angular/issues/13268 available since 4.0.0-beta.7

<select [compareWith]="compareFn" ...

Take care of if you want to access this within compareFn.

compareFn = this._compareFn.bind(this);

// or 
// compareFn = (a, b) => this._compareFn(a, b);

_compareFn(a, b) {
   // Handle compare logic (eg check if unique ids are the same)
   return a.id === b.id;
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks! Incidentally, the workaround did not work as coded. In the `updateSelectedValue` function, I had to change `JSON.parse(event)` to `JSON.parse(event.target.value)` – RHarris Mar 11 '16 at 17:02
  • Thanks for the feedback! Could you please also mention that at the answer with the workaround. I guess this is another bug introduced after the answer was posted. There are some pull requests with fixes for ` – Günter Zöchbauer Mar 11 '16 at 17:05
  • @Matthijs can you explain why `=c` rather than `=c.id` ? vesion changes or code improvements in source? – Martin Apr 20 '16 at 10:06
  • If you use `c.id` you don't need `ngValue`. `ngValue` is to support binding to object instead of binding to string. – Günter Zöchbauer Apr 20 '16 at 10:07
  • 2
    All readers have to double check that they use `ngValue` and not just `value`, even if the select displays the correct text. – koppor May 15 '16 at 20:47
  • Can you make it work if the selected item is the second in the list when the page is loaded? – AustinTX Jul 07 '16 at 13:48
  • 25
    Tried it but this does seem to data-bind only from Dropdown to model. If entering the page with model already set the dropdown is not set accordingly... – Strinder Jul 19 '16 at 16:09
  • 16
    @Strinder a frequent mistake is to use another object instance for `selectedValue` than for `c` of (the default item). A different object even with the same properties and values doesn't work, it has to be the same object instance. – Günter Zöchbauer Jul 19 '16 at 16:14
  • 1
    @GünterZöchbauer Yeah. Already thought of thought. So there's no easy way to sync directly with model and a list of values? = always via onChange? – Strinder Jul 19 '16 at 17:33
  • Sorry, I don't understand your last comment. – Günter Zöchbauer Jul 19 '16 at 17:34
  • 1
    Soon we might be able to compare the ngModel objects by their property using custom comparator function. Just watch this issue: https://github.com/angular/angular/issues/13268 – kub1x Jan 23 '17 at 14:07
  • Support for a custom comparer function was added a while ago https://github.com/angular/angular/commit/f89d004c519fd1645b9f94ff40cd740ad3658360 – Günter Zöchbauer May 26 '17 at 07:12
  • if above example is not working, try adding name attribute to select. i.e – surya Jan 02 '18 at 15:47
  • Could someone explain what the comparator does? Calls a callback? – Sampgun Feb 28 '18 at 08:58
  • @TonySamperi `compareFn` is a reference to a function that is called by Angular (if provided) to do custom comparison, for example compare the `id` property of two values not if they are identical object instances (Angular default) – Günter Zöchbauer Feb 28 '18 at 09:02
  • @GünterZöchbauer so basically it's like the ng-change of AngularJS – Sampgun Feb 28 '18 at 09:35
  • @TonySamperi I don't know AngularJS, but I doubt that is similar. `compareFn` is used to compare `selectedValue` with `countries` to determine which item should be rendered as selected. – Günter Zöchbauer Feb 28 '18 at 09:46
  • @GünterZöchbauer In AngularJS you have `...ng-change="myCallback(selectedValue)" ` which in angular 5 becomes `... (change)="myCallback(selectedValue)" ...`. From what I see the compare only wants the name of the function and automatically passes the ngModel and the ngValue of the selected...I seriously don't understand why I would need this...I mean, it could be easily achieved by using (change)... – Sampgun Feb 28 '18 at 09:57
  • @TonySamperi Ok, I now get what `ng-change` does, but `[compareWith]="compareFn"` is something entirely different. It's about to determine if two objects are equal or not and how to do the comparison. – Günter Zöchbauer Feb 28 '18 at 10:03
  • why can't I find any documentation of `[compareWith]` in [https://angular.io/docs](https://angular.io/docs) ? – th1rdey3 Jul 30 '18 at 07:40
  • 1
    It's always easy once you know it ;-) but https://angular.io/api/forms/NgSelectOption#description contains a link https://angular.io/api/forms/SelectControlValueAccessor with good docs. – Günter Zöchbauer Jul 30 '18 at 07:44
  • 3
    `[ngValue]` instead of `[value]` was the key for me. Thanks. – Falci May 01 '19 at 13:06
  • You can also make use of Template variable. Pass it inside the change event and then you can get the value which you have set in Select. – rp4361 Jul 27 '20 at 08:07
  • 1
    Link to the official documentation: https://angular.io/api/forms/NgSelectOption – Philip Nov 23 '20 at 14:48
  • This saved me since I added the `ngModel` inside `option` tag instead of adding it once in the `select` tag, so keep an eye out for it. – Abdur Rahman Nov 14 '21 at 11:12
  • 1
    [ngValue] instead of [value] was the key for me aswell! – Stef Sep 01 '22 at 20:02
56

This could help:

<select [(ngModel)]="selectedValue">
  <option *ngFor="#c of countries" [value]="c.id">{{c.name}}</option>
</select>
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
Carolina Faedo
  • 865
  • 7
  • 4
23

You can do this too without the need to use [(ngModel)] in your <select> tag

Declare a variable in your ts file

toStr = JSON.stringify;

and in you template do this

 <option *ngFor="let v of values;" [value]="toStr(v)">
      {{v}}
 </option>

and then use

let value=JSON.parse(event.target.value)

to parse the string back into a valid JavaScript object

Rahul Kumar
  • 5,120
  • 5
  • 33
  • 44
  • 3
    This indeed is doable, but on large objects will become a pain. Also, Angular's underline capability of change detection is something to be thought of. Outputting information as json, easily parsable by bots, adds to performance hauls. Using Angular's change detection hides (encapsulates) the logic of the data, and assures you of your needed information. @Günter Zöchbauer answer is the way to do it in Angular. :) – Lucaci Andrei Jan 10 '18 at 09:18
  • Helped me where I had a single list and changing one value should not update the next so it helped using this as a hack without the use of ngmodel,Thanks :) – Melvin Oct 04 '18 at 11:59
  • This works for plain JavaScript objects but note for instances of a class you'd lose all the methods on it. – KhalilRavanna May 30 '19 at 21:20
18

It worked for me:

Template HTML:

I added (ngModelChange)="selectChange($event)" to my select.

<div>
  <label for="myListOptions">My List Options</label>
  <select (ngModelChange)="selectChange($event)" [(ngModel)]=model.myListOptions.id >
    <option *ngFor="let oneOption of listOptions" [ngValue]="oneOption.id">{{oneOption.name}}</option>
  </select>
</div>

On component.ts:

  listOptions = [
    { id: 0, name: "Perfect" },
    { id: 1, name: "Low" },
    { id: 2, name: "Minor" },
    { id: 3, name: "High" },
  ];

An you need add to component.ts this function:

  selectChange( $event) {
    //In my case $event come with a id value
    this.model.myListOptions = this.listOptions[$event];
  }

Note: I try with [select]="oneOption.id==model.myListOptions.id" and not work.

============= Another ways can be: =========

Template HTML:

I added [compareWith]="compareByOptionId to my select.

<div>
  <label for="myListOptions">My List Options</label>
  <select [(ngModel)]=model.myListOptions [compareWith]="compareByOptionId">
    <option *ngFor="let oneOption of listOptions" [ngValue]="oneOption">{{oneOption.name}}</option>
  </select>
</div>

On component.ts:

  listOptions = [
    { id: 0, name: "Perfect" },
    { id: 1, name: "Low" },
    { id: 2, name: "Minor" },
    { id: 3, name: "High" },
  ];

An you need add to component.ts this function:

 /* Return true or false if it is the selected */
 compareByOptionId(idFist, idSecond) {
    return idFist && idSecond && idFist.id == idSecond.id;
 }
  • This is good if you also want to handle the change event to do something extra (like inform a change callback). Though in that case, you only need to put `[ngModel]` and then set your model manually in your custom change callback defined in `(ngModelChange)`. – crush Feb 21 '19 at 22:25
15

Just in case someone is looking to do the same using Reactive Forms:

<form [formGroup]="form">
  <select formControlName="country">
    <option *ngFor="let country of countries" [ngValue]="country">{{country.name}}</option>
  </select>
  <p>Selected Country: {{country?.name}}</p>
</form>

Check the working example here

elvin
  • 961
  • 1
  • 9
  • 26
7

In app.component.html:

<select type="number" [(ngModel)]="selectedLevel">
  <option *ngFor="let level of levels" [ngValue]="level">{{level.name}}</option>
</select>

And app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  levelNum:number;
  levels:Array<Object> = [
      {num: 0, name: "AA"},
      {num: 1, name: "BB"}
  ];

  toNumber(){
    this.levelNum = +this.levelNum;
    console.log(this.levelNum);
  }

  selectedLevel = this.levels[0];

  selectedLevelCustomCompare = {num: 1, name: "BB"}

  compareFn(a, b) {
    console.log(a, b, a && b && a.num == b.num);
    return a && b && a.num == b.num;
  }
}
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
Mojtaba Nava
  • 858
  • 7
  • 17
6

For me its working like this, you can console event.target.value.

<select (change) = "ChangeValue($event)" (ngModel)="opt">   
    <option *ngFor=" let opt of titleArr" [value]="opt"></option>
</select>
mike_t
  • 2,484
  • 2
  • 21
  • 39
nano dev
  • 335
  • 4
  • 6
5

The key is to use a two way binding in the select via [(ngModel)] and use [ngValue] in each option.

You can even have a default null option and it works with Angular 12.

<select name="typeFather" [(ngModel)]="selectedType">
  <option [ngValue]="null">Select a type</option>
  <option *ngFor="let type of types" [ngValue]="type">{{type.title}}</option>
</select>

That approach is always going to work, however if you have a dynamic list, make sure you load it before the model.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
Jack Sowell
  • 51
  • 1
  • 1
4

You Can Select the Id using a Function

<option *ngFor="#c of countries" (change)="onchange(c.id)">{{c.name}}</option>
Mohamed Gabr
  • 430
  • 5
  • 18
4

Create another getter for selected item

<form [formGroup]="countryForm">
  <select formControlName="country">
    <option *ngFor="let c of countries" [value]="c.id">{{c.name}}</option>
  </select>

  <p>Selected Country: {{selectedCountry?.name}}</p>
</form>

In ts :

get selectedCountry(){
  let countryId = this.countryForm.controls.country.value;
  let selected = this.countries.find(c=> c.id == countryId);
  return selected;
}
Rafi
  • 833
  • 13
  • 17
2

Also, if nothing else from given solutions doesn't work, check if you imported "FormsModule" inside of "AppModule", that was a key for me.

1

You can get selected value also with help of click() by passing the selected value through the function

<md-select placeholder="Select Categorie"  
    name="Select Categorie" >
  <md-option *ngFor="let list of categ" [value]="list.value" (click)="sub_cat(list.category_id)" >
    {{ list.category }}
  </md-option>
</md-select>
Jose Kj
  • 2,912
  • 2
  • 28
  • 40
1

use this way also..

<h1>My Application</h1>
<select [(ngModel)]="selectedValue">
     <option *ngFor="let c of countries" value="{{c.id}}">{{c.name}}</option>
 </select>
Rathinavel
  • 31
  • 4
0

Attention Angular 2+ users: for some reason, [value] does not work on elements. use [ngModel] instead.

<select [ngModel]="selectedCountry">
    <option *ngFor="let country of countries" [value]="country">{{country.name}}</option>
</select>
mamashare
  • 399
  • 4
  • 10
0

Tested on Angular 11. I need an extra object 'typeSelected'. Pay attention I'm not using [(ngValue)] as other answers do:

<mat-select formControlName="type" [(value)]="typeSelected" 
            [compareWith]="typeComparation">
  <mat-option *ngFor="let myType of allSurveysTypes" [value]="myType">
    {{myType.title}}
  </mat-option>
</mat-select>
//Declaration.
typeSelected: SurveyType;
...

//Assigning variable 'type' of object 'survey' to 'typeSelected'.
this.typeSelected = survey?.type;
...

    
//Function to compare SurveyType objects.
typeComparation = ( option, value ) =>  {
  if (option && value) {
    return option.id === value.id;
  }
}
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
fuegonju
  • 111
  • 1
  • 7
-2

This code is very simple:

<select class="form-control" id="marasemaat" [(ngModel)]="fullNamePresentor" 
        [formControl]="stateControl" (change)="onSelect($event.target.value)">
  <option *ngFor="let char of programInfo1;let i = index;" 
          onclick="currentSlide(9,false)" 
          value={{char.id}}>{{char.title + " "}}  ----> {{char.name + " "+ char.family }} ---- > {{(char.time.split('T', 2)[1]).split(':',2)}}</option>
</select>
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
Mojtaba Nava
  • 858
  • 7
  • 17