getForm(group: FormGroup, fields: any[]) {for(让 f 个字段){开关(f.type){案例组":group.addControl(f.name, new FormGroup({}));this.getForm(group.get(f.name) as FormGroup, f.children);休息;案例复选框":group.addControl(f.name, new FormGroup({}));const groupOption = group.get(f.name) as FormGroup;for(让选择 f.options){groupOption.addControl(opt.key, new FormControl(opt.value));}休息;案例数组":group.addControl(f.name, new FormArray([]));const array = group.get(f.name) as FormArray;如果(f.value){f.value.forEach(x=>array.push(this.addGroupArray(f.children)))array.patchValue(f.value)}休息;默认:group.addControl(f.名称,new FormControl(f.value || "", Validators.required));休息;}}
<!--在 case group 我们重复使用 field-builder作为getFormGroup"的形式并作为字段field.children"--><fieldset *ngSwitchCase="'group'"><legend>{{field.name}}</legend><field-builder *ngFor="let item of field.children";[form]="getFormGroup(field.name)";[字段]=项目"></field-builder></fieldset><!--在数组的情况下,我们创建一个表并使用该函数getFormArray"--><fieldset *ngSwitchCase="'array'"><legend>{{field.name}}</legend><table [formArrayName]=field.name"><tr><th *ngFor="let item of field.children">{{item.label}}</th><th><button class="btn btn-primary";(click)=getFormArray(field.name).push(this.addGroupArray(field.children))">添加</button></th></tr><tr *ngFor="let group of getFormArray(field.name).controls;let i=index";[formGroupName]=i"><td *ngFor="let item of field.children"><field-builder noLabel="true";[form]="getFormArray(field.name).at(i)";[字段]=项目"></field-builder></td><td><button class="btn btn-primary";(click)=getFormArray(field.name).removeAt(i)">删除</button></td></tr></fieldset>
getForm(group: FormGroup, fields: any[]) {for(让 f 个字段){开关(f.type){案例组":...休息;案例数组":....休息;默认:...休息;}}}
I had implemented the dynamic form builder in my project and it is working fine. But, now I need to add a form inside the dynamic form builder. Which will be like form inside a form. So, I had created a formarray inside my ts and it is coming correctly.
I am getting an error like
ERROR Error: Cannot find control with name: '891713'
Can anyone help me to solve this issue?Thanks in advance! :)
解决方案
puff, it's a large and complex improve the code to allow fromGroups and formArray, but we are going to try.
First we are going to change a few the DynamicFormBuilderComponent
We are going to create a function getForm to can make recursive create formGroup
getForm(group: FormGroup, fields: any[]) {
for (let f of fields) {
switch (f.type) {
case "group":
group.addControl(f.name, new FormGroup({}));
this.getForm(group.get(f.name) as FormGroup, f.children);
break;
case "checkbox":
group.addControl(f.name, new FormGroup({}));
const groupOption = group.get(f.name) as FormGroup;
for (let opt of f.options) {
groupOption.addControl(opt.key, new FormControl(opt.value));
}
break;
case "array":
group.addControl(f.name, new FormArray([]));
const array = group.get(f.name) as FormArray;
if (f.value)
{
f.value.forEach(x=>array.push(this.addGroupArray(f.children)))
array.patchValue(f.value)
}
break;
default:
group.addControl(
f.name,
new FormControl(f.value || "", Validators.required)
);
break;
}
}
See that before we create an object fields and finally add the object fields to the formGroup. Using this function we directly create an empty formGroup and we are adding FormControls or FormGroup or a FormArray using group.addControl. This allow us call recursive the function if it's necesary.
Our FieldBuilderComponent sould take account the new two types of fields: "array" and "group". I go to put it inside a fieldset
<!--in case group we repeat the field-builder using
as form the "getFormGroup" and as field "field.children"
-->
<fieldset *ngSwitchCase="'group'">
<legend>{{field.name}}</legend>
<field-builder *ngFor="let item of field.children"
[form]="getFormGroup(field.name)" [field]="item">
</field-builder>
</fieldset>
<!--in case array we create a table and use the function
"getFormArray"
-->
<fieldset *ngSwitchCase="'array'">
<legend>{{field.name}}</legend>
<table [formArrayName]="field.name">
<tr>
<th *ngFor="let item of field.children">{{item.label}}</th>
<th>
<button class="btn btn-primary" (click)="getFormArray(field.name).push(this.addGroupArray(field.children))">Add</button>
</th>
</tr>
<tr *ngFor="let group of getFormArray(field.name).controls;let i=index" [formGroupName]="i">
<td *ngFor="let item of field.children">
<field-builder noLabel="true" [form]="getFormArray(field.name).at(i)"
[field]="item">
</field-builder>
</td>
<td><button class="btn btn-primary" (click)="getFormArray(field.name).removeAt(i)">Delete</button></td>
</tr>
</table>
</fieldset>
I use two auxiliar function that only return "casted" the formGroup and the formArray
getFormGroup(field:string)
{
return this.form.get(field) as FormGroup
}
getFormArray(field:string)
{
return this.form.get(field) as FormArray
}
See how inside we call to the own component. I need add an "attribute" noLabel to not show the label in case we are mannage a FormArrayLabel
Update Really I never like "split" a series of checkbox in an array with true/false values. It's more natural that the value was, e.g. "c,f" and is checked the check box "cooking" and "fishing".
Well, the first is change the "atom checkbox" using [ngModel] and (ngModelChange). First we are going to create two auxiliar functions:
//a simple "getter" to get the value
get value() {
return this.form ? this.form.get(this.field.name).value : null;
}
//we pass "checked" and the "key" of the option
change(checked: boolean, key: any) {
const oldvalue = this.form.get(this.field.name).value || null;
//if has no value
if (!oldvalue) {
//use setValue with the "key" (if checked) or null
this.form.get(this.field.name).setValue(checked ? "" + key : null);
return;
} else {
//in value store all the options that fullfilled with the condition
const value = checked
? this.field.options.filter( //is in the old value or is the key
x => oldvalue.indexOf(x.key) >= 0 || x.key == key
)
: this.field.options.filter( //is in the old value and is not the key
x => oldvalue.indexOf(x.key) >= 0 && x.key != key
);
//we give the value null if there're no options that fullfilled
//or a join of the keys
this.form
.get(this.field.name)
.setValue(value.length > 0 ? value.map(x => x.key).join(",") : null);
}
}
Well, now we can use our [ngModel] and (ngModelChange). We need say to Angular that is a "standalone"