从 json 创建动态表单时出错找不到名称为“数据”的控件

Vik*_*iya 5 angular angular-reactive-forms angular-forms angular6 angular7

我正在使用 angular 7 应用程序,并且正在编写一个通用的动态表单创建器。我正在使用反应式表单并ng-template and <ng-container *ngTemplateOutlet>递归显示表单元素。项目的代码可以在这里看到。

但我面临以下错误

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)
Run Code Online (Sandbox Code Playgroud)

但是如果我看到我的 formGroup 对象,我可以看到一个带有数据的控件作为控件的名称,如下所示。

在此处输入图片说明

我在做什么错误?请帮忙。

问题的重现是Here stackblitz

yur*_*zui 9

你说得对,这里的 ng-template 有问题。

FormControlName指令严重依赖于当前元素之上的元素层次结构来确定FormControl. 由于您ngTemplateOutlet混淆了这个层次结构,Angular 找不到父级FormGroupFormArray控件。

您可以重构您的示例以使用嵌套组件,如果您保持 child 的层次结构,它应该可以工作AbstractControls

另一方面,ngTemplateOutlet如果您将使用FormControl指令,则可以使用它。您只需要确保为input元素提供了正确的控制。

这是如何做到的:

<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>
Run Code Online (Sandbox Code Playgroud)

这里的关键点是正确path使用我作为上下文的一部分传递到下一个子级别的目标控制。

此外,如果您想生成无限结构嵌套元素,您应该重构构建 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, []);
  }
}
Run Code Online (Sandbox Code Playgroud)

吴运行示例