Angular 6 个动态复杂模型驱动的反应形式
Angular 6 Dynamic Complex Model-Driven Reactive Forms
我正在尝试创建一个根据特定模型使用 Angular 的反应式表单动态构建的表单。该模型是一个复杂的对象,可以更改,并包含嵌套的对象和值,例如(在 TypeScript 表示法中):
model.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);
}
}
}
此代码按预期工作并根据对象创建表单。我知道这不会检查数组和 FormArray
s,因为我目前不使用它们。
构建表单后,我试图在我的模板中显示整个表单,这是我遇到问题的地方。这是我的模板:
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)
(子控件)是 FormGroup
还是 FormControl
。如果子控件是 FormGroup
,则创建 groupTemplate
的子模板,否则 controlTemplate
.
在 groupTemplate
我创建了一个 ul
节点绑定 [formGroup]
到 parent.get(name)
(子组)和 在其中 (分层)然后继续为该子组的每个控件 创建一个 formItemTemplate
模板 ,我使用 keys
管道获得其控件名称创建,对于给定的值只是 returns Object.keys()
。基本上 - 递归。
在 controlTemplate
中,我只是创建了一个新的 li
,并在其中创建了一个输入,将 [formControlName]
绑定到 [ngTemplateOutletContext]
中给出的 name
。
问题是我收到以下错误:
Cannot find control with the name: '...'
我知道如果您将 [formControlName]
绑定到一个控件上,而该控件的名称不在绑定到您的 FormGroup 的 [formGroup]
范围内,就会发生这种情况。这很奇怪,因为我什至检查了 DevTools 并且可以看到 ng-reflect-form="[object Object]"
属性应用于组的 ul
,这意味着它确实被绑定并且很可能是正确的 FormGroup
.
当调试 isGroup()
使用从模板上下文传递的附加参数(例如 model
和 name
)时,我注意到 isGroup()
被多次调用每个对象,顺序,而不是每个对象一次,像这样:
- 配置
- 数据库
- uri
- 配置 - 再次?为什么?为什么不要求
name
?
- 数据库
- uri
- 姓名
- 配置 - 第 3 次???
- 数据库
- uri
- 姓名
- 服务器 - 应该在第 5 次迭代中被调用
然后它在没有完成整个模型的迭代的情况下崩溃,除非我点击我的组件中的一些输入,这太奇怪了。
我见过 与我的相似,但对问题没有帮助。提前致谢!
通过将 [formGroup]="parent"
添加到 controlTemplate
中的 li
元素解决了我自己的问题。哎呀!
我正在尝试创建一个根据特定模型使用 Angular 的反应式表单动态构建的表单。该模型是一个复杂的对象,可以更改,并包含嵌套的对象和值,例如(在 TypeScript 表示法中):
model.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);
}
}
}
此代码按预期工作并根据对象创建表单。我知道这不会检查数组和 FormArray
s,因为我目前不使用它们。
构建表单后,我试图在我的模板中显示整个表单,这是我遇到问题的地方。这是我的模板:
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)
(子控件)是 FormGroup
还是 FormControl
。如果子控件是 FormGroup
,则创建 groupTemplate
的子模板,否则 controlTemplate
.
在 groupTemplate
我创建了一个 ul
节点绑定 [formGroup]
到 parent.get(name)
(子组)和 在其中 (分层)然后继续为该子组的每个控件 创建一个 formItemTemplate
模板 ,我使用 keys
管道获得其控件名称创建,对于给定的值只是 returns Object.keys()
。基本上 - 递归。
在 controlTemplate
中,我只是创建了一个新的 li
,并在其中创建了一个输入,将 [formControlName]
绑定到 [ngTemplateOutletContext]
中给出的 name
。
问题是我收到以下错误:
Cannot find control with the name: '...'
我知道如果您将 [formControlName]
绑定到一个控件上,而该控件的名称不在绑定到您的 FormGroup 的 [formGroup]
范围内,就会发生这种情况。这很奇怪,因为我什至检查了 DevTools 并且可以看到 ng-reflect-form="[object Object]"
属性应用于组的 ul
,这意味着它确实被绑定并且很可能是正确的 FormGroup
.
当调试 isGroup()
使用从模板上下文传递的附加参数(例如 model
和 name
)时,我注意到 isGroup()
被多次调用每个对象,顺序,而不是每个对象一次,像这样:
- 配置
- 数据库
- uri
- 配置 - 再次?为什么?为什么不要求
name
? - 数据库
- uri
- 姓名
- 配置 - 第 3 次???
- 数据库
- uri
- 姓名
- 服务器 - 应该在第 5 次迭代中被调用
然后它在没有完成整个模型的迭代的情况下崩溃,除非我点击我的组件中的一些输入,这太奇怪了。
我见过
通过将 [formGroup]="parent"
添加到 controlTemplate
中的 li
元素解决了我自己的问题。哎呀!