56

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 four selects, but only two 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 the only 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.

I can do #4 if that's the intended way, but it seems pretty clunky. Is there another approach? Am I just too early in the alpha? Did I do something silly?

import {Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';

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>

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

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

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

  printValues():void {
    console.log('strValue', this.strValue);
    console.log('objValue1', this.objValue1);
    console.log('objValue2', this.objValue2);
    console.log('objValue3', this.objValue3);
  }
}
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
user3221325
  • 621
  • 1
  • 7
  • 10
  • 5
    Dear time travellers coming here in 2016 or later! The [linked question](http://stackoverflow.com/q/35945001) has a [better answer](http://stackoverflow.com/a/35945293) which does not use hacky object-to-json-to-object conversion. – Leon Adler Sep 27 '16 at 12:42
  • 1
    True. It's weird, though, that this was marked as a duplicate of the other one when this question predated the other one by 5 months. – user3221325 Sep 28 '16 at 20:31

2 Answers2

57

I don't know what things were like in the alpha, but I'm using beta 12 right now and this works fine. If you have an array of objects, create a select like this:

<select [(ngModel)]="simpleValue"> // value is a string or number
    <option *ngFor="let obj of objArray" [value]="obj.value">{{obj.name}}</option>
</select>

If you want to match on the actual object, I'd do it like this:

<select [(ngModel)]="objValue"> // value is an object
    <option *ngFor="let obj of objArray" [ngValue]="obj">{{obj.name}}</option>
</select>
Thinkeye
  • 888
  • 12
  • 22
carrizal
  • 714
  • 7
  • 10
  • 16
    The binding works find when user select an item in the list. However, the selected item is not show correctly when the page is loaded. Can you provide plunker for working example? – AustinTX Jul 07 '16 at 13:51
  • 2
    Can't bind to 'ngModel' since it isn't a known property of 'select'. This is what i get – AJ_ Apr 06 '17 at 21:31
  • 1
    You likely need to import the formsModule – carrizal Apr 07 '17 at 15:46
17

I'm no expert with DOM or Javascript/Typescript but I think that the DOM-Tags can't handle real javascript object somehow. But putting the whole object in as a string and parsing it back to an Object/JSON worked for me:

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

@Component({
  selector: 'app',
  template: `
      <h4>Select Object via 2-way binding</h4>

      <select [ngModel]="selectedObject | json" (ngModelChange)="updateSelectedValue($event)">
        <option *ngFor="#o of objArray" [value]="o | json" >{{o.name}}</option>
      </select>

      <h4>You selected:</h4> {{selectedObject }}
  `,
  directives: [FORM_DIRECTIVES]
})
export class App {
  objArray:TestObject[];
  selectedObject:TestObject;
  constructor(){
    this.objArray = [{name: 'foo', value: 1}, {name: 'bar', value: 1}];
    this.selectedObject = this.objArray[1];
  }
  updateSelectedValue(event:string): void{
    this.selectedObject = JSON.parse(event);
  }
}
KevinM
  • 1,799
  • 4
  • 28
  • 58
DeusProx
  • 188
  • 1
  • 9
  • 2
    Thank you. I haven't tested it, but it looks like it would work. It's still non-obvious and clunky, so I hope that the ng2 team comes up with a more direct approach, but it's a definite option. – user3221325 Oct 20 '15 at 13:50
  • Thanks for your help. I posted this question (with your example added) to the issues list: https://github.com/angular/angular/issues/4843 – user3221325 Oct 21 '15 at 00:35
  • BTW, with Angular2, event comes in as an object so the `updateSelectedValue(event:string)` function needs to be changed to `updateSelectedValue(event: Object)` and the guts of the function need to change to `this.selectedObject = JSON.parse(event.target.value);` – RHarris Mar 11 '16 at 17:04
  • 2
    Also with Angular2, we can get rid of the `stringify()` function and simply use the json pipe. (ex. `[ngModel] = "selectedObject | json"` and `[value]="o | json"` – RHarris Mar 11 '16 at 17:42
  • 7
    As of [April 28](https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta17-2016-04-28), the syntax `*ngFor="#o of objArray"` is no longer valid. Now, you must use `*ngFor="let o of objArray"`. – 10gistic Sep 15 '16 at 20:24
  • You are my hero – AJ_ Apr 06 '17 at 21:34