[Fixed] Angular Deeply Nested Reactive Form: Cannot find control with path on nested FormArray

Issue

I am building a nested, dynamic Form where the User has a group, and then can nest conditions within that group, or new additional group objects within a group FormArray. Here is what the basic UI looks like. Note, not all the pieces are working, but for now I am trying to add a nested group. A nested group will work for the FormBuilder, but it gives an error and does not show correctly on the UI. The error is: ERROR Error: Cannot find control with path: 'statement -> groups -> 0 -> groups -> conditions' . Before going further, the StackBlitz can be found HERE

enter image description here

The form object looks like this:

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

Within the statement → groups → groups the user is able to push an additional FormGroup that will contain a “groups” object:

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

Long term, I expect to be able to push additional groups and further nest this Form, but for now, I am trying to get it to work on the UI. The HTML is as shown below and in this StackBlitz. I continue to get the error:
ERROR Error: Cannot find control with path: 'statement -> groups -> 0 -> groups -> conditions' , and based off several S.O. examples, I recognize that this error is due to the way my HTML is nested and the FormGroups and FormArrays, there must be an issue within it. However, I cannot seem to get it to work in order to nest and display a nested group.
Here are some approaches I have tried:

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

As a side-note, I’m not sure if this is even the best approach to implementing a nested reusable component, but I expect to research this further once I stop getting errors.

<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>

Solution

I’ve written a post at dev.to after writing this answer. Take a look.


You’re dealing with a complex form. It’s nested and it’s recursive (as you have groups in groups). I suggest you split it into more components. By doing that, you’ll make it easier to have a big picture of the whole form whenever you revisit it for any reason. And, as a very welcome cherry-on-the-cake, you will avoid the deeply nested object paths you’re using to access your controls (this can be overwhelming as you keep nesting dynamic forms the way you’re doing).

What I’m trying to say is that the error is likely caused by some silly mistake in the deep object paths you use to get access to the form parts. When dealing with this kind of complex nested form, it usually isn’t worth the effort to fix eventual issues related to wrong object paths: refactor it to get a more clean-structured component.

I strongly suggest you do the two things I’ll describe below (also, take a look at this Stackblitz demo). What I did in that demo is a complete refactor of your form and I decided not to paste all the code here because it would be excessively long, hard to read, and you wouldn’t be able to execute it anyway. So it would be pointless. Just go to the Stackblitz demo and try it there.

Your form has a very peculiar aspect: it’s recursive. So everything is gonna be easier if we don’t try to row the boat against its recursive nature stream.

I assure you I did nothing special in that code but just these two steps:

1 – Create a wrapper recursive form component:

Let’s call it GroupFormComponent. The one thing that’s not so common here is that, in the template of this component, you’re gonna have… another GroupFormComponent. Yes, you can bury an angular component inside itself recursively.

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

The above snippet helps to illustrate what I’m suggesting you do (this kind of structure shows how far you can go with angular component-based nature => it’s powerful, isn’t it?).

2 – Split your form into more controls

You can (and should) group some parts of your form into other components, to make it easier to be understood as a whole. Without any great mental effort, we can identify three components:

  • The main form, containing the overall form

  • A bar of action buttons

  • A condition component

When you assemble all these parts together, you’ll get:

<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>

To make it even more simple, <condition> and <main-form> components must implement the ControlValueAccessor interface in order to allow them to be used as FormControl‘s in other forms.

With all these this in place, you’ll have a robust, maintainable, and flexible form.

The animated gif below shows it working.

enter image description here

Leave a Reply

(*) Required, Your email will not be published