Создание динамических форм в angular2 с использованием метаданных

Я пытаюсь создать CRUD-формы, используя метаданные. Я создал очень базовую версию настраиваемого компонента, который создает форму из модели. Однако пользовательская форма в данный момент не обновляет родительскую модель.

Как я могу подойти к потоку изменений от большого контроля (см. код) к родительскому? Я думаю, что мне нужна такая функциональность, как директива ng-model. Было бы неплохо иметь расширяемое решение для проверки.

Базовая версия динамической формы:http://plnkr.co/edit/BW6hluJ0PZG5GF5gsO6G?p=preview

import {Component, View, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
import {FORM_DIRECTIVES, FORM_BINDINGS, NgFormModel, ControlGroup, Control, Validators} from 'angular2/forms';
@Component({selector: 'great-control', properties: ['column', 'value: data']})
@View({
 template: `
 <template [ng-if]="column.visible">
 <div class="pure-control-group">
 <span [ng-switch]="htmlElementType">
 <template [ng-switch-when]="'input'">
 <label [attr.for]="column.name">{{column.display}}</label>
 <input id="column.name" [attr.type]="computeInputSubType()" [(ng-model)]="value" [attr.placeholder]="column.display"> {{value}}
 </template>
 <template [ng-switch-when]="'checkbox'">
 <div class="pure-controls">
 <label for="column.name" class="pure-checkbox">
 <input id="column.name" type="checkbox" [(ng-model)]="value"> Toggle and see the change {{value}}
 </label>
 </div>
 </template>
 <template [ng-switch-when]="'option'">
 <label [attr.for]="column.name">{{column.display}}</label>
 <select [(ng-model)]="value">
 <option *ng-for="#key of column.values" [value]="key">{{key}}</option>
 </select>{{value}}
 
 </template>
 <template [ng-switch-default]"="">
 <span>Oops! This control type is unknown. Contact the creator</span>
 </template>
 </span>
 </div>
 </template>
 <template [ng-if]="!column.visible">
 There is a hidden control here because the column is set to invisible 
 <input type="hidden" [(ng-model)]="value">
 </template>
 `,
 directives: [FORM_DIRECTIVES,CORE_DIRECTIVES]
})
class GreatControl {
 column: Object;
 value: Object;
 htmlElementType:string;
 constructor() { 
 }
 onInit(){
 this.htmlElementType = this.computeHtmlElementType();
 }
 computeHtmlElementType(): string {
 if (this.column.type == "boolean") {
 return "checkbox";
 } else if (this.column.type == "enum") {
 return "option";
 } else if (this.column.type == "text" || this.column.type == "email" || this.column.type == "number"){
 return "input"
 }else{
 return "unknown"
 }
 }
 computeInputSubType(){
 if(this.column.type == "text"){
 return "text";
 } else if(this.column.type == "email"){
 return "email";
 } else if( this.column.type == "number"){
 return "number";
 } else {
 return "text";
 }
 }
}
@Component({
 selector: 'app',
 bindings: [FORM_BINDINGS]
})
@View({
 template: `
 <div><div>
 <form [ng-form-model]="form">
 <fieldset>
 <great-control *ng-for="#t of columns" [column]="t" [data]="data[t.name]">
 </great-control>
 <div>
 <button type="submit">Submit</button>
 </div>
 </fieldset>
 </form>
 </div><div><p>
 As of now this does not change. We need it to change
 </p><pre class="prettyprint linenums">{{dataString()}}
`, directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, GreatControl] }) class App { data: Object ; columns = [ {name: "column1", display:"This is number only", visible:true, type: "number", length:"10"}, {name: "column2", display:"This a text field", visible:true, type: "text", length:"10"}, {name: "column3", display:"Column 3", visible:false, type: "text", length:"10"}, {name: "column4", display:"Toggle and see", visible:true, type: "boolean"}, {name: "column5", display:"Column 5", visible:true, type: "enum", values:[ "Blue", "Yellow", "White"]} ]; constructor() { this.data= { column1 : "10" , column3: "Not a secret",column4: false, column5: "Yellow" }; } onSubmit(f) { console.log(this.data); } dataString(){ return JSON.stringify(this.data, null, 2); } valueOf(obj) { if (obj !== undefined && obj !== null) return obj; else return ""; } } bootstrap(App);

Ожидаемое поведение (не динамическое):http://plnkr.co/edit/kQ0sMT4jItvj3e5uLHtD?p=preview

import {Component, View, bootstrap, NgIf, CORE_DIRECTIVES} from 'angular2/angular2';
import {FORM_DIRECTIVES, FORM_BINDINGS, NgFormModel, ControlGroup, Control, Validators} from 'angular2/forms';
@Component({
 selector: 'app',
 bindings: [FORM_BINDINGS]
})
@View({
 template: `
 <div><div>
 <form [ng-form-model]="form">
 <fieldset>
 <div>
 <label [attr.for]="columns[0].name">{{columns[0].display}}</label>
 
 </div>
 <div>
 <label [attr.for]="columns[1].name">{{columns[1].display}}</label>
 
 </div>
 <div>
 There is a hidden control here because the column is set to invisible
 
 </div>
 <div>
 <label for="columns[3].name">
  Toggle and see the change
 </label>
 </div>
 <div>
 <label [attr.for]="columns[4].name">{{columns[4].display}}</label>
 <select [(ng-model)]="data[columns[4].name]">
 <option *ng-for="#state of columns[4].values" [value]="state">{{state}}</option>
 </select>
 </div>
 <div>
 <button type="submit">Submit</button>
 </div>
 </fieldset>
 </form>
 </div><div><pre class="prettyprint linenums">{{dataString()}}
`, directives: [FORM_DIRECTIVES, CORE_DIRECTIVES] }) class App { data: Object ; columns = [ {name: "column1", display:"This is number only", visible:true, type: "number", length:"10"}, {name: "column2", display:"This a text field", visible:true, type: "text", length:"10"}, {name: "column3", display:"Column 3", visible:false, type: "text", length:"10"}, {name: "column4", display:"Toggle and see", visible:true, type: "boolean"}, {name: "column5", display:"Column 5", visible:true, type: "enum", values:[ "Blue", "Yellow", "White"]} ]; constructor() { this.data= { column1 : "10" , column3: "Not a secret",column4: false, column5: "Yellow" }; } onSubmit(f) { console.log(this.data); } dataString(){ return JSON.stringify(this.data, null, 2); } valueOf(obj) { if (obj !== undefined && obj !== null) return obj; else return ""; } } bootstrap(App);

Любые указатели для решения этой проблемы очень ценятся. Благодарим за помощь заранее.

Источник
1 ответ

Вам нужно обернуть свое значение в объект. Примитивный тип (строка, число, логическое и ко) передаются по значению (копируется значение), если экземпляры объектов передаются по ссылке (ссылка на объект копируется).

Здесь обновленный плункер: http://plnkr.co/edit/N1W8XqIS8ykU1Vsm7hjf?p=preview

export class Value {
 value: any;
 constructor(value) {
 this.value = value;
 }
}
@Component({selector: 'great-control', properties: ['column', 'value: data']})
@View({
 template: `
 <template [ng-if]="column.visible && value != null">
 <div class="pure-control-group">
 <span [ng-switch]="htmlElementType">
 <template [ng-switch-when]="'input'">
 <label [attr.for]="column.name">{{column.display}}</label>
 <input id="column.name" [attr.type]="computeInputSubType()" [(ng-model)]="value.value" [attr.placeholder]="column.display"> {{value.value}}
 </template>
 <template [ng-switch-when]="'checkbox'">
 <div class="pure-controls">
 <label for="column.name" class="pure-checkbox">
 <input id="column.name" type="checkbox" [(ng-model)]="value.value"> Toggle and see the change {{value.value}}
 </label>
 </div>
 </template>
 <template [ng-switch-when]="'option'">
 <label [attr.for]="column.name">{{column.display}}</label>
 <select [(ng-model)]="value.value">
 <option *ng-for="#key of column.values" [value]="key">{{key}}</option>
 </select>{{value.value}}
 
 </template>
 <template [ng-switch-default]"="">
 <span>Oops! This control type is unknown. Contact the creator</span>
 </template>
 </span>
 </div>
 </template>
 <template [ng-if]="!column.visible">
 There is a hidden control here because the column is set to invisible 
 <input type="hidden" [(ng-model)]="value.value">
 </template>
 `,
 directives: [FORM_DIRECTIVES,CORE_DIRECTIVES]
})
class GreatControl {
 column: Object;
 value: Value;
 htmlElementType:string;
 constructor() { 
 }
 onInit(){
 this.htmlElementType = this.computeHtmlElementType();
 }
 computeHtmlElementType(): string {
 if (this.column.type == "boolean") {
 return "checkbox";
 } else if (this.column.type == "enum") {
 return "option";
 } else if (this.column.type == "text" || this.column.type == "email" || this.column.type == "number"){
 return "input"
 }else{
 return "unknown"
 }
 }
 computeInputSubType(){
 if(this.column.type == "text"){
 return "text";
 } else if(this.column.type == "email"){
 return "email";
 } else if( this.column.type == "number"){
 return "number";
 } else {
 return "text";
 }
 }
}

licensed under cc by-sa 3.0 with attribution.