8

Here is my problem.

Online Example of the issue

I have a dynamic JSON that I need to convert to a form. So, I used reactive forms and by going through all the properties of the JSON I create either a FormGroup or FormControl, in this way:

sampleJson ={prop1:"value1", prop2: "value2",...}

...

  myForm: FormGroup;
  myKeys=[];
    ...

  ngOnInit() {
    this.myForm = this.getFormGroupControls(this.sampleJson, this.myKeys);

  }

getFormGroupControls(json:any,keys): FormGroup{
    let controls = {};
    let value = {};

    for (let key in json) {
      if (json.hasOwnProperty(key)) {

        value = json[key];
        if (value instanceof Object && value.constructor === Object) {

          keys.push({"key":key,children:[]});
          controls[key] = this.getFormGroupControls(value,keys[keys.length-1].children);
        } else {

          keys.push({"key":key,children:[]});
          controls[key] = new FormControl(value);

        }
      }
    }

    return new FormGroup(controls);
  }

After doing so, I use recursive templates to build the form, if I do not use recursive templates I get the form to work. However with recursive templates I am getting errors:

<form [formGroup]="myForm">

  <div class="form-group">


    <ng-template #nodeTemplateRef let-node>

      <div class="node">
        <div  *ngIf="node.children.length">
          {{"section [formGroupName]="}} {{ getNodeKey(node) }}
          <section style="display:block;margin:20px;border:solid 1px blue;padding-bottom: 5px;"
            [formGroupName]="getNodeKey(node)" >
            <h1>{{ node.key }}</h1>
            <ng-template
              ngFor
              [ngForOf]="node.children"
              [ngForTemplate]="nodeTemplateRef">
            </ng-template>
          </section>
          {{"end of section"}}
        </div>
        <div  *ngIf="!node.children.length">
          <label [for]="node.key">{{node.key}}</label>&nbsp;
          <input  type="text" [id]="node.key"
                  class="form-control">
        </div>
      </div>

    </ng-template>

    <ng-template *ngFor="let myKey of myKeys"
                 [ngTemplateOutlet]="nodeTemplateRef"
                 [ngTemplateOutletContext]="{ $implicit: myKey   }">
    </ng-template>

  </div>

FormerComponent.html:25 ERROR Error: Cannot find control with name: 'road'

That corresponds to this sample JSON:

"address": {
        "town": "townington",
        "county": "Shireshire",

        "road": {
          "number": "1",
          "street": "the street"
        }

I have is being displayed, so I know the elements are there. What am I missing?

8
  • I believe [formGroupName]="road" is not aware that it's nested under the address formgroup. It's looking for a formgroup named road directly under the root [formGroup]="myForm". If you nest a road formgroup directly under myForm, you'll see the error no longer appears. Commented Jan 16, 2020 at 19:00
  • Replacing formGroupName with formGroup everywhere might fix the issue. But you'll need a way to grab the correct FormGroup instance for each nested group. Commented Jan 16, 2020 at 19:02
  • that creates this other error > Cannot create property 'validator' on string 'name' Commented Jan 16, 2020 at 20:14
  • is the dynamic json always going to return a known set? that could change but we can be aware of them and have something type safe? Commented Jan 21, 2020 at 11:12
  • I guess what I'm asking is: is it truly dynamic or is it just a oneOf from a known set of possible entries like name, personal, address etc Commented Jan 21, 2020 at 11:13

2 Answers 2

10

Problem with your current code seems to be that ng-template parent is your app component, so it doesnt take into account other formGroupNames in top templates you defined and always seek in root FormGroup.

It also seems that full group name/control name is not supported in templates (e.g. cant use formGroupName="address.road")

If you need for some reason formGroups -- you can pass them in context to templates. Or you can address formControls directly:

  • remove all formGroupName from template
  • store fullPath: keys.push({"key":key,children:[], fullKey: parent ? parent.fullKey + '.' + key: key}); (you can store FormControl instance itself as well ofc.)
  • and use it: <input type="text" [formControl]="myForm.get(node.fullKey)"

Stackblitz Example

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

Comments

2
+50

Or if you still want form groups / controls hierarchy you can use formGroup and formControl directives by passing them recursively (instead of formGroupName and formControlName)

Stackblits link

NB : same issue here : Recursive ReactiveForm cannot find formGroups inside of template

1 Comment

This one ended up working much better and required less changes to my original goal. That is why I awarding it the response since I think it is more accurate

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.