Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error during dynamic form creation from json Cannot find control with name: 'data'

I am using angular 7 appplication and i am writing a generic dynamic form creator. I am using reactive forms and ng-template and <ng-container *ngTemplateOutlet> for recursively displaying the form element.The code of the project could be seen Here.

But i am facing the following error

ERROR Error: Cannot find control with name: 'data'
    at _throwError (forms.js:1775)
    at setUpControl (forms.js:1683)
    at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective.addControl (forms.js:4532)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName._setUpControl (forms.js:5030)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName.ngOnChanges (forms.js:4980)
    at checkAndUpdateDirectiveInline (core.js:9239)
    at checkAndUpdateNodeInline (core.js:10507)
    at checkAndUpdateNode (core.js:10469)
    at debugCheckAndUpdateNode (core.js:11102)
    at debugCheckDirectivesFn (core.js:11062)

But if i see my formGroup object i can see a control with data as the name of the control as shown below.

enter image description here

What is the mistake i am doing ? Please help.

Reproduction of the problem is Here stackblitz

like image 376
Vikhyath Maiya Avatar asked Jun 28 '19 12:06

Vikhyath Maiya


People also ask

How do I add a control to a form group?

To add, update, or remove controls in FormGroup , use the following commands: addControl() adds a control and updates its value and validity. removeControl() removes a control. setControl() replaces an existing control.


1 Answers

You're right there is a problem with ng-template here.

FormControlName directive heavily relies on hierarhy of elements above current element to determine FormControl. Since you're using ngTemplateOutlet this hierarhy mixed up and Angular can't find parent FormGroup or FormArray controls.

You can refactor your example to use nested components and it should work if you will keep hierarhy of child AbstractControls.

On the other hand, you can make it work with ngTemplateOutlet if you will use FormControl directive. You need to only make sure you provided correct control to your input element.

Here's how it could be done:

<form [formGroup]="form">
    <ng-container *ngTemplateOutlet="controlList; context: {controls: schema, path: []}"></ng-container>

    <ng-template #controlList let-controls="controls" let-path="path" let-isArray="isArray">

        <ng-container *ngFor="let control of controls; let i = index;">
            <ng-container *ngIf="control?.type === 'simple'">
                <div class="control">
                    <label>
              {{ control.label }}
              <input type="text" [formControl]="form.get(isArray ? path.concat(i, control.name) : path.concat(control.name))"> 
            </label>
                </div>
            </ng-container>
            <ng-container *ngIf="['object', 'array'].indexOf(control.type) > -1">
                <fieldset>
          <legend>{{control.name}}</legend>
                    <ng-container
                        *ngTemplateOutlet="controlList; context: {controls: control.items, isArray: control.type === 'array', path: isArray ? path.concat(i, control.name) : path.concat(control.name)}">
                    </ng-container>
                </fieldset>
            </ng-container>
        </ng-container>

    </ng-template>
</form>

The key point here is to get correct path to destination control which I'm passing as part of context to next child level.

Also if you want to generate infinity structure nested elements you should refactor your code for building FormGroup:

ngOnInit() {
  this.schema = data.start.fields.schema;
  this.form = this.createFormGroup(this.schema);
} 

createFormGroup(items) {
  let group: { [controlId: string]: AbstractControl; } = {};

  items.forEach((item) => group[item.name] = this.createFormControl(item));

  return this.fb.group(group);
}

createFormControl(item) {
  if (item.type === 'array') {
    return this.fb.array(item.items.map((subItem) => {
      return this.fb.group({
        [subItem.name]: this.createFormControl(subItem)
      })
    }));
  } else if (item.type === 'object') {
    return this.createFormGroup(item.items);
  } else {
    return this.fb.control(item.value, []);
  }
}

Ng-run Example

like image 54
yurzui Avatar answered Sep 19 '22 17:09

yurzui