546

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?

1
  • Just realized my Plunk works a little differently in IE vs. Chrome. Neither one actually works the way I'm wanting, but FYI.
    – RHarris
    Commented Mar 11, 2016 at 16:22

16 Answers 16

956
<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;
}
26
  • 2
    All readers have to double check that they use ngValue and not just value, even if the select displays the correct text.
    – koppor
    Commented May 15, 2016 at 20:47
  • 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
    Commented Jul 19, 2016 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. Commented Jul 19, 2016 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
    Commented Jul 19, 2016 at 17:33
  • 3
    [ngValue] instead of [value] was the key for me. Thanks.
    – Falci
    Commented May 1, 2019 at 13:06
56

This could help:

<select [(ngModel)]="selectedValue">
  <option *ngFor="#c of countries" [value]="c.id">{{c.name}}</option>
</select>
4
  • 6
    I've used [value] instead of [ngValue]. It's not the same. This worked for me Commented May 12, 2017 at 18:53
  • 2
    Error on '#' in angular 4
    – sea-kg
    Commented Mar 28, 2018 at 9:23
  • 3
    Use let instead of # @sea-kg Commented Jul 15, 2018 at 6:05
  • 2
    This answer doesn't get the selected value
    – San Jaisy
    Commented Oct 10, 2018 at 4:24
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

3
  • 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. :) Commented Jan 10, 2018 at 9: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
    Commented Oct 4, 2018 at 11:59
  • This works for plain JavaScript objects but note for instances of a class you'd lose all the methods on it. Commented May 30, 2019 at 21:20
20

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;
 }
1
  • 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
    Commented Feb 21, 2019 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

1
  • this.form.get("country").value.Id Commented Feb 12, 2021 at 14:11
8

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;
  }
}
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>
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.

4

You Can Select the Id using a Function

<option *ngFor="#c of countries" (change)="onchange(c.id)">{{c.name}}</option>
5
  • (change) function does not fire any events for this tag but works on <select>, <textarea> and <input> Commented Jul 28, 2020 at 20:03
  • In this case we don`t need to fire any event here he needed to catch the Id of the looped list in case you need the event you need to add the (Change) event to the select element as follows (Change)="myFunction($event)" Commented Jul 29, 2020 at 11:10
  • You can find your Value in event.target.value Commented Jul 29, 2020 at 11:12
  • And also you need to add whatever the object you want in the Options element as follows <option *ngFor="loopObject of loopList" [value]="loopObject"> {{loopObject.viewProperty}} </option> Commented Jul 29, 2020 at 11:29
  • it didn't work. On the option tag but started working when I used it in the <select> tag Commented Jul 30, 2020 at 6:24
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;
}
3

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>
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>
1

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>
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;
  }
}
-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>

Not the answer you're looking for? Browse other questions tagged or ask your own question.