47

I know that similar questions have been asked, but I've found none with a good answer. I want to create a select list in an Angular form, where the value for each option is an object. Also, I do NOT want to use 2 way data binding. e.g. if my Component has these fields:


    lUsers: any[] = [
        { Name: 'Billy Williams', Gender: 'male' },
        { Name: 'Sally Ride', Gender: 'female'}
        ];
    curUser: any;

I would like my HTML template to contain this:


    <select #selectElem (change)="setNewUser(selectElem.value)">
        <option *ngFor="let user of lUsers" [ngValue]="user">
            {{user.Name}}
        </option>
    </select>

With this code, though, my setNewUser() function receives the contents of the selected user's Name field. Why it picks that specific field, I have no idea. What I expect is that it would receive the "value" of the selected option, which I specifically set to a user object.

Note that I used ngValue instead of value in the option. That was by suggestion of others on SO. If I use value instead, what happens is that the object gets converted to the string '[Object object]', which is what setNewUser() receives, which is useless.

FYI, I'm working on Windows 10, using angular 4.0.0 with @angular/cli 1.1.2. Here is the setNewUser() method:


    setNewUser(user: User): void {

    console.log(user);
    this.curUser = user;
    } // setNewUser()

I am determining just what exactly is being passed to it both my logging it, and also including this on the template: <pre>{{curUser}}</pre>

John Deighan
  • 4,329
  • 4
  • 18
  • 19

6 Answers6

64

I'm currently using [ngValue] and it stores objects just fine.

The explanation as to why you experienced issues using (change) instead of (ngModelChange) can be found in this question

So, since you've already used [ngValue], you probably want to do something like this, where you will only use one way binding in order to be able to use the ngModelChange directive:

<select (ngModelChange)="setNewUser($event)" (ngModel)="lUsers">
        <option *ngFor="let user of lUsers" [ngValue]="user">
            {{user.Name}}
        </option>
    </select>

And your ts file will capture the event and receive the User object without needing to track it by id, basically reusing your old method will be good enough:

setNewUser(user: User): void {
    console.log(user);
    this.curUser = user;
    }

UPDATE 16/06/2020:

Always depending on your use case you may need to use square brackets instead of round here: (ngModel)="lUsers. For the particular case the OP stated, round brackets was the right choice. A clear and detailed description of the difference between square/round and banana box can be found in this answer from Angular guru Günter Zöchbauer

Steven
  • 1,236
  • 1
  • 13
  • 37
26

As value attribute of option tag cannot store a whole object we will create a new property id in the lUsers array to keep track of the selected item.

HTML :

<select #selectElem (change)="setNewUser(selectElem.value)">
    <option *ngFor="let user of lUsers" [value]="user.id">
        {{user.Name}}
    </option>
</select>

This will pass the unique id to our setNewUser function on change.

In your component.ts :

...

lUsers: any[] = [
    { id: 1, Name: 'Billy Williams', Gender: 'male' },
    { id: 2, Name: 'Sally Ride', Gender: 'female'}
];
curUser: any = this.lUsers[0]; // first will be selected by default by browser

...

setNewUser(id: any): void {
    console.log(id);
    // Match the selected ID with the ID's in array
    this.curUser = this.lUsers.filter(value => value.id === parseInt(id));
    console.log(this.curUser);
}

Here is the plnkr demo of the above code. Open your developer tools to view the console logs.

Dhyey
  • 4,275
  • 3
  • 26
  • 33
  • 1
    Neither the OP's code nor yours deal with the value attribute. I'm not sure why it's mentioned in your answer. Moreover the OP tries to use ngValue, so it would be useful to add an explanation why they should bind to the value property instead (Is it better? Does the OP use it incorrectly? ...) – a better oliver Feb 20 '18 at 10:12
10

You can use [ngValue] instead of [value] on the -Elements. It works for me.

I've found that information here: https://www.reddit.com/r/Angular2/comments/65run4/select_option_using_value_vs_ngvalue/

Dieter Rehbein
  • 941
  • 8
  • 16
5

ANGULAR 6 SIMPLY DO THIS

HTML

<div class="col-3" style="float:left;padding-left: 18px !important;">
      <label>Vehicle No.</label>
      <div *ngIf="gpsDevicesArray && gpsDevicesArray.length > 0">
            <select [ngModel]="selectedVehicle" style="padding: 7px;border-radius: 4px;"
                    (ngModelChange)="onVehicleNumberChange($event)">
                    <option *ngFor=" let device of gpsDevicesArray" [ngValue]="device">
                        {{device.vehicleNumber}}
                    </option>
            </select>
      </div>
</div>

TYPESCRIPT

// For intital Value ( put this line inside server calling function )

 this.selectedVehicle = gpsDevicesArray[0];

// listener

 onVehicleNumberChange(device) {
        console.log("selectedVehicle ====", device);
 }
kumar kundan
  • 2,027
  • 1
  • 27
  • 41
3

HTML:

<select [formControl]="years">
 <option value="">Select</option>
 <option *ngFor="let year of yearsList" [ngValue]="year">{{year.value}}</option>
</select>

TS:

years = new FormControl('');
yearsList = [{id: 1, value: '2019'}, {id:2, value: '2020'}];
this.years.valueChanges.subscribe((year) => {
 console.log(year)
});

RESULT: you will get year object in console :)

Zain ul abideen
  • 131
  • 1
  • 7
2

You can use ngModel and ngValue

<select [(ngModel)]="curUser ">
    <option *ngFor="let user of lUsers" [ngValue]="user">
        {{user.Name}}
    </option>
</select>