1

在我的离子项目(最后一个版本和最后一个角度版本)中,我使用动态表单来从我的休息 api 构建动态表单。

这个想法是我的后端服务器以 json 格式的表单输入和我需要的所有属性(整个表单)返回。目的是创建动态表单,如果在我的后端编辑表单(例如添加输入或属性),我不必更新和“重新构建”我的 ionic 应用程序以便进行更改考虑到。

这就是我按照动态表单流程/教程所做的。

首先我在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/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例如,我在名为的子文件夹中创建了 sub*form 组件text.js(相对于文档中的 question-textbox.ts 文件):

import { FormBase } from '../form-base';


export class Text extends FormBase<string> {
  controlType = 'text';
  type: string;

  constructor(options: {} = {}) {
    super(options);
    this.type = options['type'] || 'text';
  }
}

我有/src/providers/rest-api/ folder名为的服务form-service.ts

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的描述,我通过运行命令(ionic cli)来创建我的组件。中的一个组件/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

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

然后我让另一个组件扩展form-base名为的组件dynamic-custom-rest-form

ts:

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

html:

<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

import { NavController, ViewController, App } from 'ionic-angular';
import { Component }                          from '@angular/core';
import { Storage }                            from '@ionic/storage';
import { FormProvider }                       from '../../providers/rest-api/form-service';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  providers: [
    FormProvider,
  ],
})
export class HomePage {

  constructor(
    public  navCtrl: NavController,
    public  loginService: LoginProvider,
    public  formService: FormProvider,
    private storage: Storage,
    private app: App,
    private viewCtrl: ViewController)
  {
    this.viewCtrl.showBackButton(false);
    this.formService.fetchFormRestView();
  }
}

src/pages/home/home.html

<ion-header>
<!-- header html -->
</ion-header>

<ion-content padding>

  <form-base [formInputs]="formService.formInputs"></form-base>

</ion-content>

调用表单服务时出现两个错误

第一的:

错误:找不到名称为“input[inputName]”的控件

第二个错误:

TypeError:无法分配给“input [inputName]”上的属性“validator”:不是对象

通过遵循 Angular 文档,我真的不明白我的错误在哪里。

谢谢。

4

0 回答 0