Binding select element to object in Angular
Asked Answered
P

16

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?

Puppy answered 11/3, 2016 at 16:15 Comment(1)
Just realized my Plunk works a little differently in IE vs. Chrome. Neither one actually works the way I'm wanting, but FYI.Puppy
H
955
<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;
}
Heroism answered 11/3, 2016 at 16:31 Comment(26)
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)Puppy
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 <select>. Hopefully they'll land soon.Perice
@Matthijs can you explain why =c rather than =c.id ? vesion changes or code improvements in source?Hostel
If you use c.id you don't need ngValue. ngValue is to support binding to object instead of binding to string.Perice
All readers have to double check that they use ngValue and not just value, even if the select displays the correct text.Pluviometer
Can you make it work if the selected item is the second in the list when the page is loaded?Kilogram
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...Cysteine
@Cysteine 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.Perice
@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?Cysteine
Sorry, I don't understand your last comment.Perice
Soon we might be able to compare the ngModel objects by their property using custom comparator function. Just watch this issue: github.com/angular/angular/issues/13268Janis
Support for a custom comparer function was added a while ago github.com/angular/angular/commit/…Perice
if above example is not working, try adding name attribute to select. i.e <select [(ngModel)]="selectedValue" name="test">Erminois
Could someone explain what the comparator does? Calls a callback?Newspaperman
@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)Perice
@GünterZöchbauer so basically it's like the ng-change of AngularJSNewspaperman
@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.Perice
@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)...Newspaperman
@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.Perice
why can't I find any documentation of [compareWith] in https://angular.io/docs ?Antediluvian
It's always easy once you know it ;-) but angular.io/api/forms/NgSelectOption#description contains a link angular.io/api/forms/SelectControlValueAccessor with good docs.Perice
[ngValue] instead of [value] was the key for me. Thanks.Tillman
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.Schauer
Link to the official documentation: angular.io/api/forms/NgSelectOptionUnarm
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.Sammy
[ngValue] instead of [value] was the key for me aswell!Raynata
G
56

This could help:

<select [(ngModel)]="selectedValue">
  <option *ngFor="#c of countries" [value]="c.id">{{c.name}}</option>
</select>
Glazier answered 15/12, 2016 at 14:41 Comment(4)
I've used [value] instead of [ngValue]. It's not the same. This worked for meGlazier
Error on '#' in angular 4Flashing
Use let instead of # @FlashingAudriaaudrie
This answer doesn't get the selected valueAby
L
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

Love answered 3/5, 2017 at 8:46 Comment(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. :)Mccourt
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 :)Beaulahbeaulieu
This works for plain JavaScript objects but note for instances of a class you'd lose all the methods on it.Uniliteral
K
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;
 }
Kahle answered 29/3, 2018 at 15:9 Comment(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).Preservative
M
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

Mintun answered 5/4, 2018 at 20:56 Comment(1)
this.form.get("country").value.IdFrancesfrancesca
R
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;
  }
}
Reprehend answered 6/10, 2019 at 8:56 Comment(0)
R
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>
Roentgenograph answered 24/12, 2017 at 6:30 Comment(0)
S
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.

Soak answered 26/11, 2019 at 12:38 Comment(0)
W
4

You Can Select the Id using a Function

<option *ngFor="#c of countries" (change)="onchange(c.id)">{{c.name}}</option>
Whitish answered 24/1, 2017 at 10:5 Comment(5)
(change) function does not fire any events for this tag but works on <select>, <textarea> and <input>Deliquesce
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)"Whitish
You can find your Value in event.target.valueWhitish
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>Whitish
it didn't work. On the option tag but started working when I used it in the <select> tagDeliquesce
W
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;
}
Wimsatt answered 14/1, 2019 at 17:26 Comment(0)
L
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.

Lakisha answered 14/1, 2018 at 21:24 Comment(0)
S
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>
Spragens answered 25/5, 2017 at 11:40 Comment(0)
K
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>
Khotan answered 3/10, 2019 at 11:59 Comment(0)
L
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>
Leolaleoline answered 25/11, 2020 at 9:59 Comment(0)
I
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;
  }
}
Immaterial answered 24/2, 2021 at 11:22 Comment(0)
R
-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>
Reprehend answered 7/9, 2020 at 6:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.