Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use select/option/NgFor on an array of objects? #4843

Closed
LMFinney opened this issue Oct 21, 2015 · 47 comments
Closed

How to use select/option/NgFor on an array of objects? #4843

LMFinney opened this issue Oct 21, 2015 · 47 comments
Assignees
Labels
effort2: days feature Issue that requests a new feature

Comments

@LMFinney
Copy link

I'm having trouble creating a select in Angular2 that is backed by an array of Objects instead of strings. I knew how to do it in AngularJS using ngOptions, but it doesn't seem to work in Angular2 (I'm using alpha 42).

In the sample below, I have five selects, but only three of them work.

  1. 'Select String' is a simple string-based select, and it works fine.
  2. 'Select Object via 2-way binding' was my attempt to use 2-way binding. Unfortunately, it fails in two ways - when the page loads, the select shows the wrong value (foo instead of bar), and when I select an option in the list, the value '[object Object]' gets sent to the backing store instead of the correct value.
  3. 'Select Object via event' was my attempt to get the selected value from $event. It fails in two ways, too - the initial load is incorrect in the same way as (2), and when I selection an option in the list, the value '[object Object]' is retrieved from the event, so I can't get the right value. The select gets cleared.
  4. 'Select Object via string' is an approach that uses an object that works. Unfortunately, it really works by using the string array from (1) and converting the value from string to object and back.
  5. 'Select Object via JSON' might be a bit better than (4) because it doesn't require the string array, but it does involve using JSON each way. Also, I'm not sure if getting the Event from (change) is better or worse than getting the stringified objected from (ng-model-change). (@Chocoloper helped me with this version)
import {Component, FORM_DIRECTIVES, NgFor, Inject} from 'angular2/angular2';
import {RouteParams} from 'angular2/router';

interface TestObject {
  name:string;
  value:number;
}

@Component({
  selector: 'app',
  template: `<h4>Select String</h4>
            <select [(ng-model)]="strValue">
                <option *ng-for="#o of strArray" [value]="o">{{o}}</option>
            </select>

            <h4>Select Object via 2-way binding</h4>
            <select [(ng-model)]="objValue1">
                <option *ng-for="#o of objArray" [value]="o">{{o.name}}</option>
            </select>

            <h4>Select Object via event</h4>
            <select [ng-model]="objValue2" (change)="updateObjValue2($event)">
                <option *ng-for="#o of objArray" [value]="o">{{o.name}}</option>
            </select>

            <h4>Select Object via string</h4>
            <select [ng-model]="objValue3.name" (change)="updateObjValue3($event)">
                <option *ng-for="#o of strArray" [value]="o">{{o}}</option>
            </select>

            <h4>Select Object via JSON</h4>
            <select [ng-model]="stringify(objValue4)" (ng-model-change)="updateObjValue4($event)">
                <option *ng-for="#o of objArray" [value]="stringify(o)">{{o.name}}</option>
            </select>

            <div><button (click)="printValues()">Print Values</button></div>`,
  directives: [FORM_DIRECTIVES, NgFor]
})
export class AppComponent {
  objArray:TestObject[] = [{name: 'foo', value: 1}, {name: 'bar', value: 1}];
  objValue1:TestObject = this.objArray[1];
  objValue2:TestObject = this.objArray[1];
  objValue3:TestObject = this.objArray[1];
  objValue4:TestObject = this.objArray[1];

  strArray:string[] = this.objArray.map((obj:TestObject) => obj.name);
  strValue:string = this.strArray[1];

  stringify(o:any):string {
    return JSON.stringify(o);
  }

  updateObjValue2(event:Event):void {
    const value:string = (<HTMLSelectElement>event.srcElement).value;

    this.objValue2 = this.objArray.find((obj:TestObject) => obj.name === value);
  }

  updateObjValue3(event:Event):void {
    const value:string = (<HTMLSelectElement>event.srcElement).value;

    this.objValue3 = this.objArray.find((obj:TestObject) => obj.name === value);
  }

  updateObjValue4(event:string):void {
    this.objValue4 = JSON.parse(event);
    // if there were functions on TestObject, JSON.parse wouldn't be enough; we'd need this:
    //this.objValue4 = this.objArray.find((obj:TestObject) => obj.name === JSON.parse(event).name);
  }

  printValues():void {
    console.log('strValue', this.strValue);
    console.log('objValue1', this.objValue1);
    console.log('objValue2', this.objValue2);
    console.log('objValue3', this.objValue3);
    console.log('objValue4', this.objValue4);
  }
}

I can do (4) or (5) if that's the intended way, but they seem pretty clunky. Is there another approach? Am I just too early in the alpha? Did I do something silly?

@mhevery mhevery added P1: urgent effort2: days feature Issue that requests a new feature labels Oct 22, 2015
@mhevery
Copy link
Contributor

mhevery commented Oct 22, 2015

@vsavkin I think we need to have something like ng-value See: https://github.com/angular/angular.dart/blob/daff0c67adb80ed3305e68cbf9c89ca92ebe9992/lib/directive/ng_model.dart#L674

@binarious
Copy link

Any news on this?

@therustmonk
Copy link

Maybe problem with select value holder type? Why string?

export class SelectControlValueAccessor implements ControlValueAccessor {
  value: string;   // Does typescript has any convert rules?
  onChange = (_) => {};
  onTouched = () => {};
...
  writeValue(value: any): void {
    this.value = value;
    this._renderer.setElementProperty(this._elementRef, 'value', value);
  }

https://github.com/angular/angular/blob/master/modules/angular2/src/common/forms/directives/select_control_value_accessor.ts

@slippyC
Copy link

slippyC commented Jan 7, 2016

I have run into this problem as well. NgFor does not seem to work correctly inside a <select> tag. Was attempting to loop through an array of state abbreviations.

@erichooshmand
Copy link

We're experiencing this too. We'd like to be able to use ngFor inside of a select and access the object representing the option when it's selected.

@zoechi
Copy link
Contributor

zoechi commented Jan 9, 2016

@shotleybuilder
Copy link

This works for test case 2:

<select [(ngModel)]="objValue.name">
<option *ngFor="#oo of objArray" [value]="oo.name">{{oo.name}}</option>
</select>

bar gets displayed on render
selecting foo has {name:"foo", value:1} returned

Or set intValue:number = this.objArray[1].value; and then

<select [(ngModel)]="intValue">
<option *ngFor="#oo of objArray" [value]="oo.value">{{oo.name}}</option>
</select>

To render the name and just return the value.

What doesn't seem to work is setting strValue:string = this.objArray[1].name; and then

<select [(ngModel)]="strValue">
  <option *ngFor="#oo of objArray" [value]="oo.name">{{oo.name}}</option>
</select>

bar gets rendered, but strValue is strangely now an object {name:"bar",value:1} and selecting foo doesn't change the value of strValue (probably because it's an object)

The [(ng-model)] syntax was throwing errors for me. I'm new to ng-2 ...

@benohead
Copy link

Actually, wouldn't setting [(ngModel)] to "objValue.name" change the name of objValue and not change the selected object when the user chooses another entry in the select box? i.e.:

initial:
objArray = [ { 'name':'name1' }, { 'name':'name2' } ]
objValue = { 'name':'name1' }

after selecting the second entry:
objArray = [ { 'name':'name2' }, { 'name':'name2' } ]
objValue = { 'name':'name2' }

@dougludlow
Copy link

Test case 2 seems logical to me, but still doesn't work in beta.1. The value appears to be converted to a string, which results in [object Object]. I'm attempting to implement some cascading dropdowns, being able to use objects as the value would make this much easier.

@CaselIT
Copy link
Contributor

CaselIT commented Feb 3, 2016

While looking at the angular2 tutorial I came across this bug while playing with the sample code https://angular.io/resources/live-examples/forms/ts/plnkr.html
It is working on chrome 49 but not on firefox 45 and edge 25.

@eliezerreis
Copy link

+1

@GregWoods
Copy link

So we can't use select. Maybe we should use radio buttons. Oh , no wait, those are broken too

@slintes
Copy link

slintes commented Feb 17, 2016

Of course you can use select, it's just not as easy as wanted / expected.
I use a string property in my component now for 2 way binding to the view, and an additional change listener which picks the corresponding object from the list of available objects.

Snippets:

private repositories: Repository[];
private selectedRepositoryName: string;
private selectedRepository: Repository;

private onRepositorySelected() {
    this.selectedRepository = this.repositories.find(repository => repository.name === this.selectedRepositoryName);
}
<select [(ngModel)]="selectedRepositoryName" (ngModelChange)="onRepositorySelected()">
    <option *ngFor="#repository of repositories" [value]="repository.name">{{repository.name}}</option>
</select>

@jondejong
Copy link

I could not get the above hack from @slintes to work at first. Interesting to note that the order of the ngModel and the ngModelChange is important. For reference, we are using 2.0.0-beta.3.

@Lanayx
Copy link

Lanayx commented Feb 23, 2016

I confirm that [(ngModel)] and (ngModelChange) on select are not working in Firefox and Edge. Any plans on fixing this?

@slintes
Copy link

slintes commented Feb 23, 2016

oh no, you're right, I only tested in Chrome until now.

@binarious
Copy link

The Firefox and IE select bug is a separate issue: #6573

@DanielYKPan
Copy link

I think this issue may cause by the two-way binding
when we set <option *ngFor="#level of levels" [value]="level">{{level.name}}, the html option's value is actually not the value of the item from the *ngFor loop.

@vvolodin
Copy link

vvolodin commented Mar 7, 2016

@slintes , thanks a lot for the workaround.

@ObaidUrRehman
Copy link

Any updates on when this is going to get fixed?

@Rarely
Copy link

Rarely commented Mar 15, 2016

+1 having to workaround with using stringify on objects right now.

@mhevery
Copy link
Contributor

mhevery commented Mar 17, 2016

OK, I have escalated it.

@LMFinney
Copy link
Author

LMFinney commented Apr 6, 2016

Thank you, @kara! I look forward to trying this out.

@kara
Copy link
Contributor

kara commented Apr 6, 2016

Update (once #7939 goes in):

To use a select with objects as option values, use [ngValue]:

<select [(ngModel)]="selectedCity">
   <option *ngFor="#city of cities" [ngValue]="city">{{city.name}}</option>
</select>
class MyComp {
   selectedCity: Object;
   cities: Object[] = [
      {name: "SF"},
      {name: "NYC"}
   ];
}

You can still use [value] the same way as before for option values that are not objects.

nuwang referenced this issue in galaxyproject/cloudlaunch-ui Apr 9, 2016
mircoservices pushed a commit to mircoservices/angular that referenced this issue Apr 10, 2016
@mLaird
Copy link

mLaird commented Apr 11, 2016

I tried the ng-value code approach, including the way the objects are set
up. (Its different than the suggestions for using ngControl.) I'm using
beta.14. I get this error: (The line numbers don't match up with anything
in my code.)
Error: Uncaught (in promise): Template parse errors:
Can't bind to 'ng-value' since it isn't a known native property
("ntrol]="residenceForm.controls['country']">

            <option *ngFor="#country of countries" [ERROR

->][ng-value]="country.name">

              {{country.name}}

            </option>

"): PostApartment4Rent@20:55
Can't bind to 'ng-value' since it isn't a known native property
("gFormControl]="residenceForm.controls['state']">

            <option *ngFor="#state of states" [ERROR ->][ng-value]="

state.name">

              {{state.name}}

            </option>

"): PostApartment4Rent@29:50

Here's how one of the objects is set:
countries: Object[] = [
{name: 'United States'}, {name:'Canada'}, {name:'Mexico'}];

On Wed, Apr 6, 2016 at 5:44 PM, Kara notifications@github.com wrote:

Update:

To use a select with objects as option values, use [ng-value]:

<select [(ng-model)]="selectedCity">

{{city.name}}

class MyComp {
selectedCity: Object;
cities: Object[] = [
{name: "SF"},
{name: "NYC"}
];
}

You can still use value the same way you did before for non-objects.


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#4843 (comment)

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

@0x-r4bbit
Copy link
Contributor

@mLaird change [ng-value] to [ngValue]. Property bindings are always camel case

@mLaird
Copy link

mLaird commented Apr 11, 2016

Thanks. With ngValue, the page loads, no Console errors, validation works,
input tags update the controls object in ngControls.
However the controls object in ngControls still does not get updates of
user input in select tags. For example, when country is selected in a
select and address is entered in an input:
controls: Object
country: Object
_pristine: true
_touched: true
_value: ""
address: Object
_pristine: false
_touched: true
_value: "4 Main Street"

On Mon, Apr 11, 2016 at 2:17 PM, Pascal Precht notifications@github.com
wrote:

@mLaird https://github.com/mLaird change [ng-value] to [ngValue].
Property bindings are always camel case


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#4843 (comment)

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

@mLaird
Copy link

mLaird commented Apr 12, 2016

I now discovered that everything works fine (all of the controls object is
updated) in Chrome. The problem occurs in Firefox. Have not tested Edge.
Is this a browser problem or an Angular2 problem?

On Mon, Apr 11, 2016 at 3:35 PM, Michael Laird michael.w.laird@gmail.com
wrote:

Thanks. With ngValue, the page loads, no Console errors, validation works,
input tags update the controls object in ngControls.
However the controls object in ngControls still does not get updates of
user input in select tags. For example, when country is selected in a
select and address is entered in an input:
controls: Object
country: Object
_pristine: true
_touched: true
_value: ""
address: Object
_pristine: false
_touched: true
_value: "4 Main Street"

On Mon, Apr 11, 2016 at 2:17 PM, Pascal Precht notifications@github.com
wrote:

@mLaird https://github.com/mLaird change [ng-value] to [ngValue].
Property bindings are always camel case


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#4843 (comment)

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

@mhevery
Copy link
Contributor

mhevery commented Apr 12, 2016

@mLaird please file a separate issue for FF.

@mLaird
Copy link

mLaird commented Apr 12, 2016

Thanks. The new issue is
#8030

On Tue, Apr 12, 2016 at 1:47 PM, Miško Hevery notifications@github.com
wrote:

@mLaird https://github.com/mLaird please file a separate issue for FF.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#4843 (comment)

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

@ankasala
Copy link

ankasala commented Jul 13, 2016

Hi i am new to angular2 and i have a small task which i am not getting.i want to display details from database by selecting a value in drop down list and date . how should i display using webapi service or normal service.please try to help me in this problem thanks in advance
my code goes here:
meeting room.txt

@mLaird
Copy link

mLaird commented Jul 13, 2016

ankasala, post your question and your current code at stackoverflow.com
Lots of developers can help you there. This site is for reporting
faults/bugs in Angular2.

On Wed, Jul 13, 2016 at 11:10 AM, ankasala notifications@github.com wrote:

Hi iam new to angular2 and i have a small task which iam not getting.i
want to display details from database by selecting a value in drop down
list and date . how should i display using webapi service or normal service.
my code goes here:
meeting room.txt
https://github.com/angular/angular/files/361762/meeting.room.txt


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4843 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/ADDSb2lcsEzJYja2I4F0MBvOK1MsmQBVks5qVP_5gaJpZM4GSnNe
.

Mike Laird

12 Arbor Creek Drive, Pittsford, New York 14534
mlaird@alum.mit.edu

". . . remembering on both sides that civility is not a sign of weakness,
and sincerity is always subject to proof. Let us never negotiate out of
fear, but let us never fear to negotiate." - John F. Kennedy

@calvinKG
Copy link

calvinKG commented Oct 5, 2016

Hi,

Guys, have you tried this testing this issues with angular 2 final.
It those not work.

My code:

<select class="form-control" [(ngModel)]="jobRole.userType" required name="userType" #userType="ngModel"> <option *ngFor="let userType of userTypes" [ngValue]="userType">{{userType.description}}</option> </select>

My json data:

{"role":{"id":68,"roleName":"TestRole2","description":"View all access pertaining to a users","parentRole":{"id":2,"roleName":"UserProfile","description":"Administer User information and roles","parentRole":"","system":{"id":3,"systemName":"USER_AUTH","description":"FNB Card User Auth","functionPrefix":"UA"},"functions":[{"id":25,"name":"UV","description":"View users","system":{"id":3,"systemName":"USER_AUTH","description":"FNB Card User Auth","functionPrefix":"UA"}},{"id":26,"name":"UA","description":"Administer user information","system":{"id":3,"systemName":"USER_AUTH","description":"FNB Card User Auth","functionPrefix":"UA"}}],"userType":{"id":1,"description":"Staff"}},"system":{"id":3,"systemName":"USER_AUTH","description":"FNB Card User Auth","functionPrefix":"UA"},"functions":[],"userType":{"id":1,"description":"Staff"}}}

@CaselIT
Copy link
Contributor

CaselIT commented Oct 5, 2016

@calvinKG just a suggestion.
Try rename the variable in the ngFor so it's not duplicated. eg
<select class="form-control" [(ngModel)]="jobRole.userType" required name="userType" #userType="ngModel"> <option *ngFor="let uT of userTypes" [ngValue]="uT">{{uT.description}}</option> </select>
Not sure if it will help.

@calvinKG
Copy link

calvinKG commented Oct 5, 2016

@CaselIT I still can't get the selected option .

On my HTML this is what I get.

image

@ChristofDC
Copy link

I can't get the selection to work as well.
My html:

<select placeholder="Maak een keuze" [(ngModel)]="selectedOffice" class="office input-lg from-control" name="selectOffice">
            <option *ngFor="let office of offices" [ngValue]="office">{{office.Name}}</option>
        </select>

The updating of ngModel on selection is working but if I set the selectedOffice variable the option isn't selected in the form control.

@jovanialferez
Copy link

i got a weird, could be similar, issue where i got a series of selects/dropdowns generated with ngFor. the number of dropdowns varies based on another dropdown. no issue rendering from intial value of the binded variable/model but weird thing is, when you change one value in a dropdown, it reflects as well in the dropdown next to it. the binded model though have the correct updated value. :)

@calvinKG
Copy link

calvinKG commented Oct 10, 2016

@CaselIT Managed to do a work around for it. Got selected value.

<select class="form-control"(change)="setSystemsValue($event.target.value)" [(ngModel)]="jobRole.system.id" name="system" #system="ngModel"> <option *ngFor="let s of systems" [value]="s.id" > {{s.systemName}} </option> </select>

setSystemsValue(value) { this.jobRole.system = this.systems.filter((currentItem:Systems) => { return currentItem["id"] == value; })[0]; }

@rkusuma
Copy link

rkusuma commented Nov 21, 2016

I have similar problem too.

<select id="country" class="form-control" name="selectedCountry" [(ngModel)]="state.country" required>
        <option value="">Please Select</option>
        <option *ngFor="let country of countries" [ngValue]="country">{{country.name}}</option>
</select>

When i change the select state.country have the correct value. but when i try to set state.country default value in ngOnInit (state.country = countries[0]) the select will be empty (the countries[0] not selected)

Any solution?

@zoechi
Copy link
Contributor

zoechi commented Nov 21, 2016

@rkusuma full plunker to reproduce required

@rkusuma
Copy link

rkusuma commented Nov 21, 2016

@zoechi sorry it's working now. because i'm using redux, the value that i passing to ngModel have different reference.

@belloyang
Copy link

belloyang commented Jan 24, 2018

@LMFinney changing [value] to [ngValue] solves the problem for me.
for example:

<select [(ngModel)]="selectedObj">
<option *ngFor="let obj of myObjArray" [ngValue]="obj">
</select>

@tluanga34
Copy link

@belloyang Your answer works great. Thanks, you save my day.

@janusred23
Copy link

janusred23 commented Jan 24, 2019

how about, when I select a country, country name will show. And then the country code/zip code will show automatically to the input text box.

<select class="form-control" formControlName="countryId"> <option value="">Select Country</option> <option *ngFor="let country of countries" value={{country.id}}> {{country.name}} </option> </select> <input class="form-control" type="text" formControlName="countryCode"> <!-- Show Country Code/Zip Code -->

and in my component file/TS file:

countries: Object[];
getCountry() {
this.countryService.getCountry().subscribe(
resp => {
this.countries = resp['data']['countries'];
}
);
}

I'm just new in Angular, I hope someone can help me. TIA!

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
effort2: days feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests