首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >角度回复表单>用户自定义表单错误

角度回复表单>用户自定义表单错误
EN

Stack Overflow用户
提问于 2019-06-02 21:27:04
回答 2查看 218关注 0票数 0

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

代码语言:javascript
复制
export interface FormModel {
  id: string;
  name: string;
  type: 'group' | 'control';
  children: FormModel[];
  isArray: boolean;
}

样本结构如下:

代码语言:javascript
复制
[{
    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
  }
  ];

在基于从DB加载的JSON创建反应性表单之后,我很难创建相应的html,因为它有递归。

经过多次尝试,我成功地获得了以下内容,其中它生成了一个与我所理解的类似的HTML:

代码语言:javascript
复制
<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>

我使用的模板如下:

代码语言:javascript
复制
<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>

在我看来,这是对的,但我总是犯错误:

代码语言:javascript
复制
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

你们能帮我找出问题吗?我在这方面工作了两天,但没有成功。

谢谢!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-06-03 18:48:18

正如Ersenkoening所说,对于使用FormsGroup和Form数组,您可以直接使用“控件”。使用FormArrayName,表单组等,可能是一个真正的头痛。

请确保Ersenkoening的form-array.component.html可以编写得更简单一些,如

代码语言:javascript
复制
<div [formGroup]="formArray">
    <ng-container *ngFor="let child of formArray.controls; let i = index">
        <app-form-group [formGroup]="child"></app-form-group>
    </ng-container>
</div>

是的,对FormArray来说是另一种方式,但是请记住,formArray只是一个“特殊”的FormGroup。

有了这个想法,更新就会更深入,在html中只使用formControl,所以我们需要将“控件”作为变量传递。请参阅stackblitz

表单字段视图类似于

代码语言:javascript
复制
<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
    <div *ngFor="let item of formFields;let i=index">
        {{item.id}}
        <ng-container *ngTemplateOutlet="recursiveList; context:{ 
            $implicit: formFields[i], 
            <!--see how pass the control of myForm--->
            control:myForm.get(item.id) }">
        </ng-container>
    </div>

    <ng-template #recursiveList let-item let-control="control">
        <div *ngIf="!item.children">
            {{item.id}}<input [formControl]="control">
    </div>
    <div *ngIf="item.children">
      <div *ngIf="!item.isArray">
        <div *ngFor="let children of item.children">
            <ng-container *ngTemplateOutlet="recursiveList; context:{
                 $implicit:children, 
                 <!--see how pass the control of a formGroup--->
                 control:control.get(children.id)}">
            </ng-container>
        </div>
      </div>
      <div *ngIf="item.isArray">
        <div *ngFor="let children of item.children;let i=index">
            <ng-container *ngTemplateOutlet="recursiveList; context:{ 
                  $implicit:children, 
                 <!--see how pass the control of a formArray--->
                  control:control.at(i)}">
            </ng-container>
        </div>
      </div>
    </div>
    </ng-template>
</form>
<pre>
{{myForm?.value|json}}
</pre>

更新2简化了表单的创建,请参阅stackblitz

代码语言:javascript
复制
  ngOnInit() {
    let group = new FormGroup({});
    this.formFields.forEach(element => {
      let formItem = this.createFormGroup(element);
      group.addControl(element.id, formItem);
    });

    this.myForm = group;
  }

  private createFormGroup(formItem: FormModel) {

    if (formItem.type=="group")
    {
      if (formItem.isArray && formItem.children.length<formItem.minQtd)
      {
        const add={...formItem.children[0]}
        //here we can "clean" the value
        while (formItem.children.length<formItem.minQtd)
        formItem.children.push(add)
      }
      let group:FormGroup=new FormGroup({});
      let controls:any[]=[]
      formItem.children.forEach(element=>{
        let item=this.createFormGroup(element);
        if (formItem.isArray)
           controls.push(item);
        else
          group.addControl(element.id, item);
      })
      if (!formItem.isArray)
        return group;

      return new FormArray(controls)
    } 
    if (formItem.type=="control")
      return new FormControl();
  }
票数 1
EN

Stack Overflow用户

发布于 2019-06-03 10:20:46

对于FormArray,生成的html需要以不同的顺序排列。您需要将formArrayName="users"分配给外部html元素,在该html元素中需要[formGroupName]="i",其中i是数组中FormControl或FormGroup的当前索引。

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

代码语言:javascript
复制
<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模板,但如果确实需要,可以使用ng模板进行相同的操作。

因此,基本上,我更改了FormArray的顺序和绑定,我使用了FormGroup、FormArrays和FormControls对象本身,而不是使用模板中的FormGroup/FormControl值(如isFormArray )来确定需要使用哪个模板。

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

启动组件

代码语言:javascript
复制
<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
  <app-form-group [formGroup]="myForm.get('variables')"></app-form-group>
</form>

form-group.component.ts

代码语言:javascript
复制
<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

代码语言:javascript
复制
  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

代码语言:javascript
复制
 <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>

这是分叉式的堆栈闪电战

Note

如果将<form>中的表单元素拆分为不同的子组件,则需要对任何元素进行FormGroup绑定,例如,简单地使用一个<div>

此实现期望所有FormArray项都是FormGroups。如果情况并非总是如此,那么您需要添加这样的信息。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56419065

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档