javascript - Angular 深度嵌套响应式表单 : Cannot find control with path on nested FormArray

标签 javascript html angular

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

enter image description here

表单对象如下所示:

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

语句 → groups → 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>

最佳答案

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


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

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

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

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

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

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

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

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

上面的代码片段有助于说明我建议您做的事情(这种结构显示了您可以在基于 Angular 组件的性质方面走多远 => 它很强大,不是吗?)。

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>

为了让它更简单,<condition><main-form>组件必须实现ControlValueAccessor接口(interface),以允许它们用作 FormControl的其他形式。

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

下面的动画 gif 显示它正在工作。

enter image description here

关于javascript - Angular 深度嵌套响应式表单 : Cannot find control with path on nested FormArray,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62352286/

相关文章:

angular - 预期参数 1-3,但得到 0

css - 如何从 Angular 垫菜单中删除选择框?

javascript - 是否可以使用 Node.js 创建 XLS excel 文件并控制单元格类型?

html - 如何将鼠标悬停在文本(稍后是链接)上并显示其旁边的图像和文本

html - html (firefox) 中的文本显示在 ubuntu 和 windows 7 中显示不同,为什么?

javascript - 如何将图像/图标添加到 Highcharts 中的图表

javascript - 这是在 Angular 2 中实现 Pipe 的正确方法吗

javascript - extjs饼图所有图例显示相同颜色

javascript - 使用javascript获取div元素的值并将其放入数组中

javascript - fancybox 和响应式文件管理器中的图像预览不起作用(空字符串传递给 getElementById()。)