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?

Dalorzo
  • 19,834
  • 7
  • 55
  • 102
  • 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. – Alex K Jan 16 '20 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. – Alex K Jan 16 '20 at 19:02
  • that creates this other error > Cannot create property 'validator' on string 'name' – Dalorzo Jan 16 '20 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? – maxime1992 Jan 21 '20 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 – maxime1992 Jan 21 '20 at 11:13
  • @maxime1992 it is always a different JSON in any possible structure – Dalorzo Jan 21 '20 at 16:58
  • Ok, never used it but I've heard a few times of https://formly.dev/guide/getting-started doing a great job for that – maxime1992 Jan 21 '20 at 17:05
  • Because you're using Reactive Forms, you don't need to bind `FormGroup` or `FormGroupName`. You just need to bind the inputs, e.g. `[formControl]="exampleControl".` Reactive Forms use the structure given to parent FormGroup / FormArray as containing data structure. See https://angular.io/guide/forms-overview#key-differences – Dane Brouwer Jan 24 '20 at 07:43

2 Answers2

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

Dane Brouwer
  • 2,827
  • 1
  • 22
  • 30
Petr Averyanov
  • 9,327
  • 3
  • 20
  • 38
2

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

Neji Sghair
  • 174
  • 5
  • 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 – Dalorzo Jan 28 '20 at 15:41