我试图在我的角度应用程序上实现一个功能,用户将能够创建自己的表单(选择字段/验证/等)。一旦他们创建了表单,我将把它的JSON结构存储在DB上供以后使用:
export interface FormModel {
id: string;
name: string;
type: 'group' | 'control';
children: FormModel[];
isArray: boolean;
}样本结构如下:
[{
id: 'users',
name: 'Users',
type: 'group',
isArray: true,
children: [
{
id: "user",
name: "User",
type: 'group',
isArray: false,
children: [
{
id: "name",
name: "Name",
type: 'control',
children: null,
isArray: false
},
{
id: "age",
name: "Age",
type: 'control',
children: null,
isArray: false
}
]
},
{
id: "user",
name: "User",
type: 'group',
isArray: false,
children: [
{
id: "name",
name: "Name",
type: 'control',
children: null,
isArray: false,
},
{
id: "age",
name: "Age",
type: 'control',
children: null,
isArray: false
}
]
}
]
}, {
id: 'comments',
name: 'Comments',
type: 'control',
children: null,
isArray: false
}
];在基于从DB加载的JSON创建反应性表单之后,我很难创建相应的html,因为它有递归。
经过多次尝试,我成功地获得了以下内容,其中它生成了一个与我所理解的类似的HTML:
<div formGroupName="users">
<div formArrayName="0">
<div formGroupName="user">
<input type="text" formControlName="name">
<input type="text" formControlName="age">
</div>
</div>
<div formArrayName="0">
<div formGroupName="user">
<input type="text" formControlName="name">
<input type="text" formControlName="age">
</div>
</div>
</div>我使用的模板如下:
<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
<div formGroupName="variables">
<ng-template #recursiveList let-controls let-prefix="prefix">
<ng-container *ngFor="let item of controls; let i = index;">
<input type="text" [formControlName]="item.id" *ngIf="(item?.children?.length > 0) == false">
<div *ngIf="item?.children?.length > 0 && item.isArray" [formArrayName]="item.id">
<ng-container
*ngTemplateOutlet="recursiveArray; context:{ $implicit: item.children, prefix: item.isArray }">
</ng-container>
</div>
<div *ngIf="item?.children?.length > 0 && !item.isArray" [formGroupName]="item.id">
<ng-container
*ngTemplateOutlet="recursiveList; context:{ $implicit: item.children, prefix: item.isArray }">
</ng-container>
</div>
</ng-container>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: formFields, prefix: '' }">
</ng-container>
<ng-template #recursiveArray let-controls let-prefix="prefix">
<ng-container *ngFor="let item of controls; let i = index;">
<div [formGroupName]="i">
<input type="text" [formControlName]="item.id" *ngIf="(item?.children?.length > 0) == false">
<div *ngIf="item?.children?.length > 0 && item.isArray" [formArrayName]="item.id">
<ng-container
*ngTemplateOutlet="recursiveArray; context:{ $implicit: item.children, prefix: item.isArray }">
</ng-container>
</div>
<div *ngIf="item?.children?.length > 0 && !item.isArray" [formGroupName]="item.id">
<ng-container
*ngTemplateOutlet="recursiveList; context:{ $implicit: item.children, prefix: item.isArray }">
</ng-container>
</div>
</div>
</ng-container>
</ng-template>
</div>
</form>在我看来,这是对的,但我总是犯错误:
ERROR
Error: Cannot find control with path: 'variables -> 0'
ERROR
Error: Cannot find control with path: 'variables -> 0 -> user'我用示例创建了一个stackblitz:https://stackblitz.com/edit/angular-mtbien
你们能帮我找出问题吗?我在这方面工作了两天,但没有成功。
谢谢!
发布于 2019-06-03 18:48:18
正如Ersenkoening所说,对于使用FormsGroup和Form数组,您可以直接使用“控件”。使用FormArrayName,表单组等,可能是一个真正的头痛。
请确保Ersenkoening的form-array.component.html可以编写得更简单一些,如
<div [formGroup]="formArray">
<ng-container *ngFor="let child of formArray.controls; let i = index">
<app-form-group [formGroup]="child"></app-form-group>
</ng-container>
</div>是的,对FormArray来说是另一种方式,但是请记住,formArray只是一个“特殊”的FormGroup。
有了这个想法,更新就会更深入,在html中只使用formControl,所以我们需要将“控件”作为变量传递。请参阅stackblitz
表单字段视图类似于
<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
<div *ngFor="let item of formFields;let i=index">
{{item.id}}
<ng-container *ngTemplateOutlet="recursiveList; context:{
$implicit: formFields[i],
<!--see how pass the control of myForm--->
control:myForm.get(item.id) }">
</ng-container>
</div>
<ng-template #recursiveList let-item let-control="control">
<div *ngIf="!item.children">
{{item.id}}<input [formControl]="control">
</div>
<div *ngIf="item.children">
<div *ngIf="!item.isArray">
<div *ngFor="let children of item.children">
<ng-container *ngTemplateOutlet="recursiveList; context:{
$implicit:children,
<!--see how pass the control of a formGroup--->
control:control.get(children.id)}">
</ng-container>
</div>
</div>
<div *ngIf="item.isArray">
<div *ngFor="let children of item.children;let i=index">
<ng-container *ngTemplateOutlet="recursiveList; context:{
$implicit:children,
<!--see how pass the control of a formArray--->
control:control.at(i)}">
</ng-container>
</div>
</div>
</div>
</ng-template>
</form>
<pre>
{{myForm?.value|json}}
</pre>更新2简化了表单的创建,请参阅stackblitz
ngOnInit() {
let group = new FormGroup({});
this.formFields.forEach(element => {
let formItem = this.createFormGroup(element);
group.addControl(element.id, formItem);
});
this.myForm = group;
}
private createFormGroup(formItem: FormModel) {
if (formItem.type=="group")
{
if (formItem.isArray && formItem.children.length<formItem.minQtd)
{
const add={...formItem.children[0]}
//here we can "clean" the value
while (formItem.children.length<formItem.minQtd)
formItem.children.push(add)
}
let group:FormGroup=new FormGroup({});
let controls:any[]=[]
formItem.children.forEach(element=>{
let item=this.createFormGroup(element);
if (formItem.isArray)
controls.push(item);
else
group.addControl(element.id, item);
})
if (!formItem.isArray)
return group;
return new FormArray(controls)
}
if (formItem.type=="control")
return new FormControl();
}发布于 2019-06-03 10:20:46
对于FormArray,生成的html需要以不同的顺序排列。您需要将formArrayName="users"分配给外部html元素,在该html元素中需要[formGroupName]="i",其中i是数组中FormControl或FormGroup的当前索引。
所以你在寻找这样的结构:
<div formArrayName="FORM_ARRAY_NAME"
*ngFor="let item of orderForm.get('items').controls; let i = index;">
<div [formGroupName]="i">
<input formControlName="FORM_CONTROL_NAME">
</div>
Chosen name: {{ orderForm.controls.items.controls[i].controls.name.value }}
</div>这里是一篇很好的文章,描述了FormArray的正确设置。
说了这话后,我用叉子叉了一下你的堆栈闪电战,看了一眼。我将FormArray和FormGroups移动到单独的组件中,而不是使用ng模板,但如果确实需要,可以使用ng模板进行相同的操作。
因此,基本上,我更改了FormArray的顺序和绑定,我使用了FormGroup、FormArrays和FormControls对象本身,而不是使用模板中的FormGroup/FormControl值(如isFormArray )来确定需要使用哪个模板。
您的问题的可能解决方案如下所示:
启动组件
<form name="myForm" [formGroup]="myForm" fxLayout="column" fxFlex>
<app-form-group [formGroup]="myForm.get('variables')"></app-form-group>
</form>form-group.component.ts
<div [formGroup]="formGroup"> // necessary because the <form> tag is outside this component
<ng-container *ngFor="let key of controlKeys">
<ng-container *ngIf="!isFormArray(key) && !isFormGroup(key)">
<p>
<label>{{key}}</label>
<input type="text" [formControlName]="key">
</p>
</ng-container>
<ng-container *ngIf="isFormArray(key)">
<app-form-array [formArray]="formGroup.get(key)" [formArrayName]="key" [parentFormGroup]="formGroup" ></app-form-array>
</ng-container>
<ng-container *ngIf="isFormGroup(key)">
<app-form-group [formGroup]="formGroup.get(key)"></app-form-group>
</ng-container>
</ng-container>
</div>form-froup.component.ts
public isFormArray(key: string): boolean {
return this.formGroup.get(key) instanceof FormArray
}
public isFormGroup(key: string): boolean {
return this.formGroup.get(key) instanceof FormGroup
}
get controlKeys() {
return Object.keys(this.formGroup.controls);
}form-array.component.html
<div [formGroup]="parentFormGroup">
<div [formArrayName]="formArrayName">
<ng-container *ngFor="let child of formArray.controls; let i = index">
<div [formGroupName]="i">
<app-form-group [formGroup]="child"></app-form-group>
</div>
</ng-container>
</div>
</div>Note
如果将<form>中的表单元素拆分为不同的子组件,则需要对任何元素进行FormGroup绑定,例如,简单地使用一个<div>。
此实现期望所有FormArray项都是FormGroups。如果情况并非总是如此,那么您需要添加这样的信息。
https://stackoverflow.com/questions/56419065
复制相似问题