动态形式 Angular 中的 FormArray

Dav*_*ebj 5 typescript form-control angular angular-reactive-forms formarray

我已经实现了一个 stackblitz,您可以在其中使用某些配置创建动态表单。一切正常,直到您使用FormArray. 有什么想法吗?基本上,您的配置文件中可以有许多不同类型的字段。例如checkbox,等等textnumber select在某些情况下,您应该有更复杂的结构,因此您应该有一个FormArray. 在这个 stackblitz 中,我试图代表正在发生的事情

https://stackblitz.com/edit/angular-dynamic-form-b​​uilder-gycshd?file=app/dynamic-form-b​​uilder/dynamic-form-b​​uilder.component.ts

因此,当我找到一个array类型时,我将创建一个 FormArray 而不是一个简单的FormControl字段列表

 public fields: any[] = [
    {
      type: "text",
      name: "firstName",
      label: "First Name",
      required: true
    },
    {
      type: "text",
      name: "lastName",
      label: "Last Name",
      required: true
    },
    {
      type: "text",
      name: "email",
      label: "Email",
      required: true
    },
    {
      type: "text",
      name: "description",
      label: "Description",
      required: true,
      multiLine: true
    },
    {
      type: "dropdown",
      name: "country",
      label: "Country",
      value: "in",
      required: true,
      options: [{ value: "in", label: "India" }, { value: "us", label: "USA" }]
    },
    {
      type: "array",
      name: "users",
      label: "Users",
      structure: "users.json"
    }
  ];
Run Code Online (Sandbox Code Playgroud)

这是我的动态表单生成器组件

export class DynamicFormBuilderComponent implements OnInit {
  @Output() onSubmit = new EventEmitter();
  @Input() fields: any[] = [];
  form: FormGroup;
  allDatas: any;
  constructor() {}

  ngOnInit() {
    let fieldsCtrls = {};
    this.allDatas = getData();
    for (let f of this.fields) {
      if (f.type != "array") {
        fieldsCtrls[f.name] = new FormControl(
          this.allDatas[f.name] || "",
          Validators.required
        );
      } else {
        fieldsCtrls[f.name] = new FormArray([
          new FormControl(this.allDatas[f.name] || "", Validators.required)
        ]);
      }
    }
    this.form = new FormGroup(fieldsCtrls);
  }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我从 allDatas 设置了一个值(如果存在),并使用 FormControls 和 FormArray 创建了 FormGroup。但是当我生成时arraybox出现错误。这是显示 FormArray 控件的 arraybox 组件:

import { HttpClient } from "@angular/common/http";
import { Component, Input, OnInit } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { ConfigService } from "../../config.service";

@Component({
  selector: "arraybox",
  template: `
    <div [formGroup]="form">
      <div formArrayName="{{ field.name }}">
        <div
          *ngFor="let obj of arrayFileds; index as idx"
          [formGroupName]="idx"
        >
          <input
            attr.type="{{ obj.type }}"
            class="form-control"
            placeholder="{{ obj.name }}"
            id="{{ obj.name }}"
            name="{{ obj.name }}"
            formControlName="{{ idx }}"
          />
        </div>
      </div>
    </div>
  `
})
export class ArrayBoxComponent implements OnInit {
  @Input() field: any = {};
  @Input() form: FormGroup;
  arrayFileds: any = [];

  get isValid() {
    return this.form.controls[this.field.name].valid;
  }
  get isDirty() {
    return this.form.controls[this.field.name].dirty;
  }

  constructor(private config: ConfigService) {}

  ngOnInit(): void {
    this.arrayFileds = this.config.getData(this.field.structure);
    console.log(this.arrayFileds);
  }
}
Run Code Online (Sandbox Code Playgroud)

getData() 返回该 FormArray 的结构。在这种情况下

[{
    "label": "Username",
    "name": "userName",
    "type": "text"
}, {
    "label": "User email",
    "name": "userEmail",
    "type": "text"
}]
Run Code Online (Sandbox Code Playgroud)

控制台中的错误是:

ERROR
Error: Cannot find control with path: 'users -> 0 -> 0'
ERROR
Error: Cannot find control with path: 'users -> 1'
Run Code Online (Sandbox Code Playgroud)

我真的不知道这里出了什么问题。如何使用 FormArray 在此表单中显示字段及其值?

注意。如果我使用 http 解析 json,stackblitz 会出现错误,所以现在我直接使用 json 的导入。基本上,this.field.structure在这个例子中没有使用(但应该是)。

Eld*_*dar 3

嗯,干得不错,但你需要改变一些事情才能实现你想要的。

  1. 您需要更改初始化表单数组的方式。您已在您的命名控件中FormArray,因此您需要将其推FormGroup入您的FormArray
 for (let f of this.fields) {
      if (f.type != "array") {
        fieldsCtrls[f.name] = new FormControl(
          this.allDatas[f.name] || "",
          Validators.required
        );
      } else { // changed part
        fieldsCtrls[f.name] = new FormArray([
          ...this.allDatas[f.name].map(
            r =>
              new FormGroup(
                Object.entries(r).reduce((acc, [k, v]) => {
                  acc[k] = new FormControl(v || "", Validators.required);
                  return acc;
                }, {})
              )
          )
        ]);
      }
Run Code Online (Sandbox Code Playgroud)

上面的代码基本上生成了FormGroup对象键为FormControls

  1. 您需要更改ArrayBox组件模板,如下所示:
 <input
            attr.type="{{ obj.type }}"
            class="form-control"
            placeholder="{{ obj.name }}"
            id="{{ obj.name }}"
            name="{{ obj.name }}"
            formControlName="{{ obj.name }}"
          />
Run Code Online (Sandbox Code Playgroud)

你有formControlName="{{ idx }}"这将导致 Angular 寻找一个FormControl命名0 1,这就是你得到错误的原因之一Error: Cannot find control with path: 'users -> 0 -> 0'。另一个原因是您直接添加FormControls 而不是FormGroups。正如您的ArrayBox模板所述FormArray有一系列FormGroup

工作堆栈闪电战

@End.Game 注意到数组部分仍有一些部分需要修复。问题的根源在于模板ArrayBox。您正在迭代配置对象来呈现表单。但由于它是一个,FormArray您还需要重复该模板来计算 的计数FormArray。所以你需要另一个*ngFor来实现这一点:

   <div [formGroup]="form">
      <div formArrayName="{{ field.name }}">
        <div *ngFor="let frm of formArray.controls; index as idx">
          <div formGroupName="{{ idx }}">
            <div *ngFor="let obj of arrayFileds;">
              <input
                attr.type="{{ obj.type }}"
                class="form-control"
                placeholder="{{ obj.name }}"
                id="{{ obj.name }}"
                name="{{ obj.name }}"
                formControlName="{{ obj.name }}"
              />
            </div>
            <hr />
          </div>
        </div>
      </div>
    </div>
Run Code Online (Sandbox Code Playgroud)

另请注意,已将idx变量移至父循环。