首页 > 解决方案 > Angular 6 动态复杂模型驱动的反应形式

问题描述

我正在尝试使用 Angular 的响应式表单创建一个根据某个模型动态构建的表单。模型是一个可以改变的复杂对象,并且包含嵌套的对象和值,例如(在 TypeScript 表示法中):

模型.d.ts

interface Model {
    // ... other stuff
    config: { // complex object which can change, here's an example for some structure
        database: {
            uri: string;
            name: string;
        };
        server: {
            endpoint: {
                host: string;
                port: number;
            };
            requestTimeoutMs: number;
        };
    };
}

使用这个对象,我建立了一个FormGroup这样的:

form.component.ts

@Component({
    // ...
    templateUrl: './form.component.html'
})
export class FormComponent {
    @Input() model: Model;
    public form: FormGroup;

    // instantiating the whole form
    public ngOnInit(): void {
        this.form = new FormGroup({
            // ... instantiating other controls using the model,
            config: new FormGroup({}),
        });
        // building the config form using model.config which is dynamic
        this.buildForm(this.form.get('config') as FormGroup, this.model.config);
    }

    // helper functions used in the template
    public isGroup(control: AbstractControl): control is FormGroup {
        return control instanceof FormGroup;
    }
    public typeOf(value: any): string {
        return typeof value;
    }

    // building the config form
    private buildForm(group: FormGroup, model: object): void {
        for (const key in obj) {
            const prop = obj[key];
            let control: AbstractControl;
            if (prop instanceof Object) { // nested group
                control = new FormGroup({});
                this.buildForm(control as FormGroup, prop);
            } else { // value
                control = new FormControl(prop);
            }
            group.addControl(control);
        }
    }
}

此代码按预期工作并根据对象创建表单。我知道这不会检查数组和FormArrays,因为我目前不使用它们。

构建表单后,我试图在我的模板中显示整个表单,这就是我遇到问题的地方。这是我的模板:

form.component.html

<form [ngForm]="form" (ngSubmit)="...">
    <!-- ... other parts of the form -->
    <ng-container [ngTemplateOutlet]="formItemTemplate"
                  [ngTemplateOutletContext]="{parent: form, name: 'config': model: model; level: 0}">
    </ng-container>
    <ng-template #formItemTemplate let-parent="parent" let-name="name" let-model="model" let-level="level">
        <ng-container [ngTemplateOutlet]="isGroup(parent.get(name)) ? groupTemplate : controlTemplate"
                      [ngTemplateOutletContext]="{parent: parent, name: name, model: model, level: level}">
        </ng-container>
    </ng-template>
    <ng-template #groupTemplate let-parent="parent" let-name="name" let-model="model" let-level="level">
        <ul [formGroup]="parent.get(name)">
            <li>{{name | configName}}</li>
            <ng-container *ngFor="let controlName of parent.get(name) | keys">
                <ng-container [ngTemplateOutlet]="formItemTemplate"
                              [ngTemplateOutletContext]="{name: controlName, model: model[name], level: level + 1}">
                </ng-container>
            </ng-container>
        </ul>
    </ng-template>
    <ng-template #controlTemplate let-name="name" let-model="model" let-level="level">
        <li class="row">
            <div class="strong">{{name | configName}}</div>
            <ng-container [ngSwitch]="typeOf(obj[name])">
                <input type="text" *ngSwitchCase="'string'" [formControlName]="name">
                <input type="number" *ngSwitchCase="'number'" [formControlName]="name">
                <input type="checkbox" *ngSwitchCase="'boolean'" [formControlName]="name">
            </ng-container>
        </li>
    </ng-template>
</form>

让我为你分解一下,以防组件的模板不被理解:

formItemTemplate的工作是区分parent.get(name)(子控件)是 aFormGroup还是 a FormControl。如果子控件是FormGroup,则创建 的子模板groupTemplate,否则controlTemplate

groupTemplate我创建了一个ul绑定[formGroup]parent.get(name)(子组)的节点并在其中(分层地)然后继续为该子组的每个控件formItemTemplate创建一个模板,我使用我创建的管道获得其控件名称,它只是返回对于给定的值。基本上 - 递归。keysObject.keys()

controlTemplate我只是创建一个新的li并在其中创建一个绑定[formControlName]name给定 in的输入[ngTemplateOutletContext]

问题是我收到以下错误:

找不到名称为“...”的控件

我知道如果您绑定的控件的名称在绑定到您的 FormGroup[formControlName]的范围内找不到,就会发生这种情况。[formGroup]这很奇怪,因为我什至检查了 DevTools 并且可以看到该ng-reflect-form="[object Object]"属性应用于ul组的 ,这意味着它确实被绑定并且很可能是正确的FormGroup.

isGroup()当使用从模板上下文传递的附加参数(例如modeland )进行调试时,name我注意到isGroup()每个对象按顺序调用了多次,而不是每个对象调用一次,如下所示:

  1. 配置
  2. 数据库
  3. 乌里
  4. 配置 - 再次?为什么?为什么不要求它name
  5. 数据库
  6. 乌里
  7. 姓名
  8. 配置 - 第三次???
  9. 数据库
  10. 乌里
  11. 姓名
  12. server - 应该在第 5 次迭代中调用

然后它在没有完成整个模型的迭代的情况下崩溃,除非我点击我的组件中的一些输入,这太奇怪了

我见过这个问题与我的类似,但对问题没有帮助。提前致谢!

标签: angularangular-reactive-formsangular-forms

解决方案


通过添加[formGroup]="parent"到中的li元素解决了我自己的问题controlTemplate。哎呀!


推荐阅读