【问题标题】:Angular Deeply Nested Reactive Form: Cannot find control with path on nested FormArrayAngular Deeply Nested Reactive Form:在嵌套的 FormArray 上找不到带有路径的控件
【发布时间】:2020-10-02 17:51:06
【问题描述】:

我正在构建一个嵌套的动态表单,其中用户有一个组,然后可以在该组中嵌套条件,或者在group FormArray 中嵌套新的其他组对象。这是基本 UI 的样子。请注意,并非所有部分都有效,但现在我正在尝试添加一个嵌套组。嵌套组适用于 FormBuilder,但它会给出错误并且不会在 UI 上正确显示。错误是:ERROR Error: Cannot find control with path: 'statement -> groups -> 0 -> groups -> conditions'。在继续之前,可以找到 StackBlitz HERE

表单对象如下所示:

{
  "statement": {
    "groups": [
      {
        "conjunctor": null,
        "conditions": [
          {
            "variable": ""
          }
        ],
        "groups": []
      }
    ]
  }
}

在statement → groups → groups 中,用户可以推送一个额外的FormGroup,其中包含一个“组”对象:

 {
 "conjunctor": null,
   "conditions": [
     {
       "variable": ""
     }
    ],
   "groups": []
 }

从长远来看,我希望能够推送其他组并进一步嵌套此表单,但现在,我正试图让它在 UI 上工作。 HTML 如下所示和in this StackBlitz。我继续收到错误: ERROR Error: Cannot find control with path: 'statement -> groups -> 0 -> groups -> conditions' ,并基于几个 S.O.例如,我认识到此错误是由于我的 HTML 嵌套方式以及 FormGroups 和 FormArrays 造成的,其中一定存在问题。但是,我似乎无法让它工作以嵌套和显示嵌套组。 以下是我尝试过的一些方法:

Angular FormArray: Cannot find control with path

Angular: Cannot find control with path: 'variable-> 0 -> id'

Angular 7 and form arrays error of cannot find a control with path

ERROR Error: Cannot find control with path

作为旁注,我不确定这是否是实现嵌套可重用组件的最佳方法,但我希望在我停止出错后进一步研究这一点。

<form [formGroup]="form">
  <div formArrayName="statement">
    <div formArrayName="groups">
      <div *ngFor="let group of form.get('statement.groups')['controls']; let i = index">
        <fieldset>
          <legend>Group {{ i + 1 }}:</legend>
          <div [formGroupName]="i">
            <span style="float: right;">
              <button type="button" style="float: right; cursor: pointer; margin-left: 5px;" (click)="deleteGroup(i)">
                delete group
              </button>
              <button type="button" style="cursor: pointer; margin-left: 5px;" (click)="addNestedGroup(i)">
                add nested group
              </button>
              <button
                type="button"
                style="cursor: pointer; margin-left: 5px;"
                (click)="addNewCondition(group.controls.conditions)"
              >
                add condition
              </button>
            </span>
            <div formArrayName="conditions">
              <div *ngFor="let condition of group.get('conditions')['controls']; let j = index">
                <fieldset>
                  <legend>Condition {{ j + 1 }}</legend>
                  <div [formGroupName]="j">
                    <input style="vertical-align: middle;" type="text" formControlName="variable" />
                    <button
                      style="float: right; margin-bottom: 5px;"
                      (click)="deleteCondition(group.controls.conditions, j)"
                    >
                      delete condition
                    </button>
                  </div>
                </fieldset>
              </div>
            </div>
            <ng-container>
              <div formArrayName="groups">
                <div *ngFor="let num of group.get('groups').value; let idx = index">
                  <fieldset>
                    <legend>Group {{ 2 }}:</legend>
                    <span style="float: right;">
                      <button
                        type="button"
                        style="float: right; cursor: pointer; margin-left: 5px;"
                        (click)="deleteGroup(0)"
                      >
                        delete group
                      </button>
                      <button type="button" style="cursor: pointer; margin-left: 5px;" (click)="addNestedGroup(0)">
                        add nested group
                      </button>
                      <button
                        type="button"
                        style="cursor: pointer; margin-left: 5px;"
                        (click)="addNewCondition(num.conditions)"
                      >
                        add condition
                      </button>
                    </span>
                    <div formArrayName="conditions">
                      <div *ngFor="cond; of: group.controls; let k = index">
                        <fieldset>
                          <legend>Condition {{ k + 1 }}</legend>
                          <div [formGroupName]="k">
                            <input style="vertical-align: middle;" type="text" formControlName="variable" />
                            <button
                              style="float: right; margin-bottom: 5px;"
                              (click)="deleteCondition(group.controls.conditions, k)"
                            >
                              delete condition
                            </button>
                          </div>
                        </fieldset>
                      </div>
                    </div>
                  </fieldset>
                </div>
              </div>
            </ng-container>
          </div>
        </fieldset>
      </div>
    </div>
  </div>
</form>

【问题讨论】:

  • 您想要有多少个嵌套级别?我可以解决您当前的问题,但我认为您会要求提供更复杂的示例
  • 最终我希望有 3-4 层深度(与现在显示的 2 层相比):组 → 组。从长远来看,我想象有组→组→组→组。尽可能深入地嵌套

标签: javascript html angular


【解决方案1】:

写完这个答案后,我在dev.to 写了一篇文章。看看吧。


您正在处理一个复杂的表单。它是嵌套的并且是递归的(因为您有组中的组)。我建议您将其拆分为更多组件。通过这样做,无论出于何种原因重新访问它,您都可以更轻松地了解整个表单。而且,作为一个非常受欢迎的小蛋糕,您将避免使用深度嵌套的对象路径来访问您的控件(当您以您正在做的方式保持嵌套动态表单时,这可能会让人不知所措)。

我想说的是,错误可能是由您用来访问表单部件的深层对象路径中的一些愚蠢错误引起的。在处理这种复杂的嵌套形式时,通常不值得努力修复与错误对象路径相关的最终问题:重构它以获得结构更清晰的组件。

我强烈建议你做我将在下面描述的两件事(另外,看看这个Stackblitz demo)。我在那个演示中所做的是对您的表单进行了完整的重构,我决定不在这里粘贴所有代码,因为它会太长,难以阅读,而且您无论如何都无法执行它。所以这将毫无意义。只需转到Stackblitz demo 并在那里尝试。

您的表单有一个非常特殊的方面:它是递归的。所以,如果我们不试图逆着它的递归自然流划船,一切都会变得更容易。

我向你保证,我在该代码中没有做任何特别的事情,只是这两个步骤:

1 - 创建一个包装递归表单组件:

我们称之为GroupFormComponent。在这里不常见的一件事是,在此组件的模板中,您将拥有...另一个GroupFormComponent。是的,您可以递归地在其内部埋入一个角度组件。

@Component({
  selector: 'group-form',
  template: `
    ...
    <!-- here you nest another instance of this component -->
    <group-form *ngIf="nestCondition"></group-form>
    ...
  `,
})
export class GroupFormComponent {...}

上面的 sn-p 有助于说明我建议你做的事情(这种结构显示了你可以在多大程度上使用基于角度组件的性质 => 它很强大,不是吗?)。

2 - 将表单拆分为更多控件

您可以(并且应该)将表单的某些部分分组到其他组件中,以使其更容易被理解为一个整体。无需任何巨大的脑力劳动,我们就可以确定三个组成部分:

  • 主窗体,包含整体窗体

  • 操作按钮栏

  • 条件组件

当你将所有这些部件组装在一起时,你会得到:

<main-form>
  <action-buttons></action-buttons>
  <condition></condition>
  <condition></condition>
  ...
  <condition></condition>

  <!-- The recursive part -->
  <main-form></main-form>
  <main-form></main-form>
  ...
  <main-form></main-form>
</main-form>

为了更简单,&lt;condition&gt; 和&lt;main-form&gt; 组件必须实现ControlValueAccessor 接口,以允许它们以其他形式用作FormControl。

有了所有这些,您将拥有一个健壮、可维护且灵活的表单。

下面的动画 gif 显示了它的工作原理。

【讨论】:

  • 这太不可思议了。多么棒的方法。非常感谢 StackBlitz 以及对它的解释。一切都很好。
  • 我的荣幸。我喜欢这样做。事实上,我想我会写一篇关于它的博文。
  • 我很乐意阅读并在您撰写时分享。如果您碰巧记得,请随意分享这些 cmets。再次感谢
  • 尝试在你的表单组件中实现Validator接口。我已经在我的答案中分叉了那个 stackblitz,并在其中一个组件中组合了一个 simple validator example。
猜你喜欢
  • 2018-12-24
  • 1970-01-01
  • 2018-07-15
  • 2018-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-27
  • 2017-10-28
相关资源
最近更新 更多