[typescript] How to use select/option/NgFor on an array of objects in Angular2

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

This question is related to typescript angular

The answer is


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>

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

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7