0

Recently, I asked the following question on StackOverflow and received an amazing response that resulted in building a recursive nested Angular Form: Angular Deeply Nested Reactive Form: Cannot find control with path on nested FormArray . Here is a blog post written by the author about the same question

This recursive nested Angular Form lets me continue to build group FormArrays, all of which have a group object:

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

You can add multiple instances of conditions and further nest the groups as shown in this StackBlitz

Essentially, the Form component has been split up into multiple components: group-control , condition-form, and action-buttons-bar (the buttons to emit actions). This approach does everything I need, however, I am having troubles patching form values via this recursive approach.

So far I have tried sending in a static statement object and patching it both into the entire form itself, as well as specifically targeting the groups object. Here is what this looks like in the

AppComponent:

(I have tried the same and similar approaches within the group-control component as well). Eventually I plan on using data from an API to populate and patch these values, but for now and testing, using this static data

  ngOnInit() {
    const data = {
      statement: {
        groups: [
          {
            conjunctor: "and",
            conditions: [
              {
                variable: "asdf"
              }
            ],
            groups: []
          }
        ]
      }
    };
    this._form.patchValue(data);
    console.log(this._form.value);
  }

Is it possible to patch the form value of the statement FormGroup on a recursive Form?

1 Answer 1

3

Stackblitz demo

There's nothing wrong with your data. It's likely a matter of timing. The form must be stable before you can patch any value. As you're doing it in OnInit, I suppose you're building the form in the constructor. If so, the form isn't ready to receive any values yet.

The simplest way to solve that is with an uncomfortable setTimeout. But I suppose that this is just for testing. In real cases, the data will probably get some time to be available anyway.

Your example above will work if you wrap it with the aforementioned setTimeout:

ngOnInit() {
  setTimeout(() => {
    const data = {
      statement: {
        groups: [
          {
            conjunctor: "and",
            conditions: [
              {
                variable: "asdf"
              }
            ],
            groups: []
          }
        ]
      }
    };
    this._form.patchValue(data);
    console.log(this._form.value);
  }, 50);
}

The animated gif below shows a patch in 3 nested groups, the last one being nested on the second one.

enter image description here

Building the form according to given data

One interesting thing you can do is build the form automatically according to the data you have. You can achieve that by analyzing the data that gets into the ControlValueAccessor.writeValue:

if (!value) {
  return;
}
setTimeout(() => {
  if (value && value.conditions && value.conditions.length) {
    this._conditionsFormArray.clear();
    value.conditions.forEach(c => this._addCondition());
  }

  if (value && value.groups && value.groups.length) {
    this._groupsFormArray.clear();
    value.groups.forEach(g => this._addGroup());
  }

  this._form.patchValue(value);
}, 50);

Maybe you've noticed the setTimeout above, it's there because we have another setTimeout in the form creation method (called on OnInit) that add a condition to the form after it's created, and the creation of the condition is, itself, inside a setTimeout. Because of that, we must wait it runs, otherwise this._conditionsFormArray.clear(); wouldn't have any effect => no condition would've been created when it runs.

private _createFormGroup() {
  this._form = this._fb.group({
    conjunctor: null,
    conditions: this._fb.array([]),
    groups: this._fb.array([])
  });

  // add one condition on the next tick, after the form creation
  setTimeout(() => this._addCondition());
}

Then with some adjustments, we can get to the following result (the gif below is based on this other Stackblitz demo):

enter image description here

Sign up to request clarification or add additional context in comments.

10 Comments

This works well, but what about the nested items? If you pass in an object with a nested group, the second group does not seem to patch the value
It's also working. To test it, in the stackblitz, I increased the delay for 5 seconds just to give me time to create two nested groups in Group 1 and, inside the second nested group, create one more even more nested. I'll put a gif on my answer to show that.
Thank you. It seems this isn't possible to add the groups according to the patch, right? As in patching this data and then having the UI show accordingly. Rather than clicking the addGroup buttons, it will get the response (from data obj) and then display on the UI
Let me see if I'm following it right: You want to patch and, based on the data, create dynamically the corresponding form, is that right?
This is great. I noticed the add group button no longer adds the condition though, so in the group-control writeValue I added .length to the if statements within the setTimeout
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.