How to use select/option/NgFor on an array of objects in Angular2
TypescriptAngularTypescript Problem Overview
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.
- 'Select String' is a simple string-based select, and it works fine.
- '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.
- '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.
- '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);
}
}
Typescript Solutions
Solution 1 - Typescript
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>
Solution 2 - Typescript
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);
}
}