在我的离子项目(带有最后一个角度版本的最后一个版本)中,我使用Dynamic Forms来从我的其余api构建动态表单。
我的想法是我的后端服务器以json格式返回表单输入和我需要的所有属性(整个表单)。目的是创建动态表单,如果在我的后端编辑表单(例如添加输入或属性),我不必更新和“重新构建”我的离子应用程序以便我的更改被采取考虑到了。
所以这就是我所做的,遵循动态表单流程/教程。
首先我在ionic app.module.ts
文件中声明导入import { ReactiveFormsModule } from '@angular/forms';
,然后导入导入模块:
@NgModule({
declarations: [
MyApp,
HomePage,
// ...
FormBaseComponent,
DynamicCustomRestFormComponent,
],
imports: [
BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp),
ReactiveFormsModule,
IonicStorageModule.forRoot(),
],
// ...
然后我在名为/src/models/
的{{1}}文件夹中创建了表单基本模型(相对于Question model):
form-base.ts
我在export class FormBase<T>{
value: T;
key: string;
label: string;
required: boolean;
controlType: string;
name: string;
fullName: string;
id: number;
disabled: boolean;
attr: Array<any>;
errors: Array<any>;
constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
controlType?: string,
name?: string,
fullName?: string,
id?: number,
disabled?: boolean,
attr?: Array<any>,
errors?: Array<any>,
} = {} ) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.controlType = options.controlType || '';
this.name = options.name || '';
this.fullName = options.fullName || '';
this.id = options.id;
this.disabled = !!options.disabled;
this.attr = options.attr;
this.errors = options.errors;
}
中以例如/src/models/form-components
命名的子文件夹中创建了太子*表单组件(相对于doc中的question-textbox.ts文件):
text.js
我在名为import { FormBase } from '../form-base';
export class Text extends FormBase<string> {
controlType = 'text';
type: string;
constructor(options: {} = {}) {
super(options);
this.type = options['type'] || 'text';
}
}
的{{1}}中提供服务:
/src/providers/rest-api/ folder
作为doc上Question form components的desfribe,我通过运行命令form-service.ts
(离子cli)来创建我的组件。 import { Validators,
FormControl,
FormGroup } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable, Inject } from '@angular/core';
import { FormBase } from '../../models/form-base';
import { Text } from '../../models/form-components/text';
import { Http } from '@angular/http';
import { FORM_API_SUFFIX } from "../services.constants";
import { EnvVariables } from '../../app/environment-variables/environment-variables.token';
import 'rxjs/add/operator/map';
@Injectable()
export class FormProvider {
protected url: URL;
protected suffix: string;
public formInputs: FormBase<any>[] = [];
constructor(public http: Http, @Inject(EnvVariables) public envVariables) { }
// fetch form data by rest api
fetchFormRestView(name: string = null) {
let formName = name;
this.suffix = FORM_API_SUFFIX + formName;
this.url = new URL(this.suffix, this.envVariables.apiEndpoint);
this.http.get(this.url.href)
.subscribe(
data => {
let formData = this.getChildrenField(JSON.parse(data['_body'].toString()));
return this.buildForm(this.createInputFiedlType(formData));
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
console.log('An error occurred:', err.error.message);
} else {
console.log(`the fetchFormFields function get a server returned code ${err.status});
}
}
);
}
// iterate on each form field from data recovered by the rest form api
getChildrenField(data) {
let fieldsArray: Array<Object> = [];
// loop on the keys directly
Object.keys(data['children']).forEach( key => {
fieldsArray[key] = data['children'][key];
});
return fieldsArray;
}
createInputFiedlType(data) {
let fieldInfo: Array<Object> = [];
// console.info("form field in array here => ", data);
// The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
Object.keys(data).forEach( key => {
// console.log(key); //key
fieldInfo = data[key]['vars'];
// console.log(fieldInfo); //value
// swith to manage and instanciate input type field for ForBase Class
switch (fieldInfo['block_prefixes'][1]) {
// ezpublish/symfony choice field
case 'choice' :
this.formInputs.push(
// I have many type of field, I put only the text component but it's the same logic for all
// text field
default:
this.formInputs.push(
new Text({
key: fieldInfo['full_name'],
name: fieldInfo['name'],
fullName: fieldInfo['full_name'],
label: fieldInfo['label'],
value: fieldInfo['value'],
required: fieldInfo['required'],
id: fieldInfo['id'],
disabled: fieldInfo['disabled'],
attr: fieldInfo['attr'],
errors: fieldInfo['errors']
}),
);
break;
}
});
return this.formInputs;
}
// build the form
buildForm(formInputs: FormBase<any>[]) {
let group: any = {};
formInputs.forEach(input => {
group = ({
[input.key] :
input.required ? new FormControl(input.value || '', Validators.required)
: new FormControl(input.value || ''),
});
});
return new FormGroup(group);
}
}
中的一个组件。
ionic generate component
:
/src/components/form-base/
form-base.ts
:
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormBase } from '../../models/form-base';
import { FormProvider } from '../../providers/rest-api/form-service';
@Component({
selector: 'form-base',
templateUrl: 'form-base.html',
providers: [ FormProvider ]
})
export class FormBaseComponent implements OnInit {
@Input() formInputs: FormBase<any>[] = [];
form: FormGroup;
payLoad: string = '';
constructor(private formService: FormProvider) { }
/**
* @method ngOnInit
* @return {FormGroup}
*/
ngOnInit() {
return this.form = this.formService.buildForm(this.formInputs);
}
/**
* @method onSubmit
* @return {[type]}
*/
onSubmit() {
return this.payLoad = JSON.stringify(this.form.value);
}
}
然后我让另一个组件扩展名为form-base.html
的{{1}}组件:
ts:
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let input of formInputs">
<app-form-input [input]="input" [form]="form"></app-form-input>
</div>
<div>
<button ion-button type="submit" [disabled]="!form.valid">Valider</button>
</div>
</form>
<div *ngIf="payLoad">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
html:
form-base
最后,在我的页面中,这里是我的离子视图的页面组件,我有这个:
dynamic-custom-rest-form
:
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormBase } from '../../models/form-base';
@Component({
selector: 'app-form-input',
templateUrl: './dynamic-custom-rest-form.html'
})
export class DynamicCustomRestFormComponent {
@Input() input: FormBase<any>;
@Input() form: FormGroup;
}
<div [formGroup]="form">
<label [attr.for]="input.key">{{input.label}} :</label>
<div [ngSwitch]="input.controlType">
<input
*ngSwitchCase="'text'"
[formControlName]="input.key"
[formControl]="input.key"
[id]="input.key"
[type]="input.controlType"
[value]="input.value"
[name]="input.name"
[placeholder]="input.name"
/>
</div>
<div class="errorMessage" *ngIf="!isValid">{{input.label}} is required</div>
</div>
:
src/pages/home/home.ts
调用表单服务时出现两个错误
首先:
错误:无法找到名称为:'input [inputName]'
的控件
第二个错误:
TypeError:无法分配给属性“validator” “input [inputName]”:不是对象
通过遵循Angular文档,我真的不明白我的错误在哪里。
感谢。