首页 > 解决方案 > Angular Reative Forms > 用户自定义表单错误

问题描述

我正在尝试在我的角度应用程序上实现一个功能,用户将能够创建自己的表单(选择字段/验证/等)。一旦他们创建了表单,我会将它的 JSON 结构存储在数据库中以供以后使用:

export interface FormModel {
  id: string;
  name: string;
  type: 'group' | 'control';
  children: FormModel[];
  isArray: boolean;
}

一个示例结构是这样的:

[{
    id: 'users',
    name: 'Users',
    type: 'group',
    isArray: true,
    children: [
      {
        id: "user",
        name: "User",
        type: 'group',
        isArray: false,
        children: [
          {
            id: "name",
            name: "Name",
            type: 'control',
            children: null,
            isArray: false
          },
          {
            id: "age",
            name: "Age",
            type: 'control',
            children: null,
            isArray: false
          }
        ]
      },
      {
        id: "user",
        name: "User",
        type: 'group',
        isArray: false,
        children: [
          {
            id: "name",
            name: "Name",
            type: 'control',
            children: null,
            isArray: false,
          },
          {
            id: "age",
            name: "Age",
            type: 'control',
            children: null,
            isArray: false
          }
        ]
      }
    ]
  }, {
    id: 'comments',
    name: 'Comments',
    type: 'control',
    children: null,
    isArray: false
  }
  ];

在基于从数据库加载的 JSON 创建反应式表单后,我在创建相应的 html 时遇到了困难,因为它具有递归。

经过多次尝试,我设法得到以下内容,它会生成一个类似于我理解应该需要的 HTML:

<div formGroupName="users">
  <div formArrayName="0">
    <div formGroupName="user">
      <input type="text" formControlName="name">
      <input type="text" formControlName="age">
    </div>
  </div>
  <div formArrayName="0">
    <div formGroupName="user">
      <input type="text" formControlName="name">
      <input type="text" formControlName="age">
    </div>
  </div>
</div>

我使用的模板如下:

<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
    <div formGroupName="variables">

        <ng-template #recursiveList let-controls let-prefix="prefix">
            <ng-container *ngFor="let item of controls; let i = index;">

                <input type="text" [formControlName]="item.id" *ngIf="(item?.children?.length > 0) == false">

            <div *ngIf="item?.children?.length > 0 && item.isArray" [formArrayName]="item.id">
                <ng-container
                    *ngTemplateOutlet="recursiveArray; context:{ $implicit: item.children, prefix: item.isArray }">
                </ng-container>
            </div>
            <div *ngIf="item?.children?.length > 0 && !item.isArray" [formGroupName]="item.id">
                <ng-container
                    *ngTemplateOutlet="recursiveList; context:{ $implicit: item.children, prefix: item.isArray }">
                </ng-container>
            </div>
        </ng-container>
    </ng-template>
    <ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: formFields, prefix: '' }">
    </ng-container>

    <ng-template #recursiveArray let-controls let-prefix="prefix">
        <ng-container *ngFor="let item of controls; let i = index;">

            <div [formGroupName]="i">

                <input type="text" [formControlName]="item.id" *ngIf="(item?.children?.length > 0) == false">

                <div *ngIf="item?.children?.length > 0 && item.isArray" [formArrayName]="item.id">
                    <ng-container
                        *ngTemplateOutlet="recursiveArray; context:{ $implicit: item.children, prefix: item.isArray }">
                    </ng-container>
                </div>
                <div *ngIf="item?.children?.length > 0 && !item.isArray" [formGroupName]="item.id">
                    <ng-container
                        *ngTemplateOutlet="recursiveList; context:{ $implicit: item.children, prefix: item.isArray }">
                    </ng-container>
                </div>
            </div>

        </ng-container>
    </ng-template>
  </div>

</form>

在我看来这是正确的,但我不断收到错误:

ERROR
Error: Cannot find control with path: 'variables -> 0'
ERROR
Error: Cannot find control with path: 'variables -> 0 -> user'

我用示例创建了一个 stackblitz: https ://stackblitz.com/edit/angular-mtbien

你们能帮我找出问题吗?我已经为此工作了 2 天,但没有任何成功:(

谢谢!

标签: angularangular-reactive-forms

解决方案


您生成的 html 需要以不同的顺序用于 FormArray。您需要将 分配formArrayName="users"给外部 html 元素,并且在该 html 元素内,您需要数组[formGroupName]="i"iFormControl 或 FormGroup 的当前索引。

所以你正在寻找这样的结构:

<div formArrayName="FORM_ARRAY_NAME"
  *ngFor="let item of orderForm.get('items').controls; let i = index;">
  <div [formGroupName]="i">
    <input formControlName="FORM_CONTROL_NAME">
  </div>

  Chosen name: {{ orderForm.controls.items.controls[i].controls.name.value }}
</div>

是一篇很好的文章,描述了 FormArray 的正确设置。


话虽如此,我分叉了你的堆栈闪电战并看了一下。我将 FormArray 和 FormGroups 移动到单独的组件中,而不是使用 ng-template,但如果你真的需要,你可以使用 ng-template 来做同样的事情。

所以基本上我改变的是 FormArray 的顺序和绑定,我使用 FormGroup、FormArrays 和 FormControls 对象本身,而不是使用模板中的 isFormArray 等 FormGroup/FormControl 值来确定需要使用哪个模板。

您的问题的可能解决方案如下所示:

起始组件

<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
  <app-form-group [formGroup]="myForm.get('variables')"></app-form-group>
</form>

form-group.component.ts

<div [formGroup]="formGroup"> // necessary because the <form> tag is outside this component
  <ng-container *ngFor="let key of controlKeys">
    <ng-container *ngIf="!isFormArray(key) && !isFormGroup(key)">
      <p>
        <label>{{key}}</label>
        <input type="text" [formControlName]="key">
      </p>
    </ng-container>
    <ng-container *ngIf="isFormArray(key)">
      <app-form-array [formArray]="formGroup.get(key)" [formArrayName]="key" [parentFormGroup]="formGroup" ></app-form-array>
    </ng-container>
    <ng-container *ngIf="isFormGroup(key)">
      <app-form-group [formGroup]="formGroup.get(key)"></app-form-group>
    </ng-container>
  </ng-container>
</div>

form-froup.component.ts

  public isFormArray(key: string): boolean {
    return this.formGroup.get(key) instanceof FormArray
  }

  public isFormGroup(key: string): boolean {
    return this.formGroup.get(key) instanceof FormGroup
  }

  get controlKeys() {
    return Object.keys(this.formGroup.controls);
  }

form-array.component.html

 <div [formGroup]="parentFormGroup">
  <div [formArrayName]="formArrayName">
    <ng-container *ngFor="let child of formArray.controls; let i = index">
      <div [formGroupName]="i">
        <app-form-group [formGroup]="child"></app-form-group>
      </div>
    </ng-container>
  </div>
</div>

这是分叉的堆栈闪电战

笔记

如果将 a 中的表单元素拆分<form>为不同的子组件,则需要在任何元素上使用 FormGroup-Binding,例如简单地<div>

此实现要求所有 FormArray 项都是 FormGroups。如果情况并非总是如此,您将需要添加它。


推荐阅读