angular - 动态表单生成器与formarray角度
问题描述
我已经在我的项目中实现了动态表单构建器,它工作正常。但是,现在我需要在动态表单构建器中添加一个表单。这就像表单中的表单。所以,我在我的 ts 中创建了一个 formarray 并且它是正确的。
我收到一个错误,例如
ERROR Error: Cannot find control with name: '891713'
这就是我实施的 https://angular.io/guide/dynamic-form
我使用的代码示例是https://stackblitz.com/edit/angular-dynamic-form-builder
所以我在文件夹'atoms'中创建了一个名为formControl的组件,如果控件来自Control,我再次调用字段控件。
表单控件组件
<td *ngFor="let itm of items; let i = index" formArrayName="childs" >
<field-builder [field]="itm" [form]="form" ></field-builder>
</td>
FormControl 组件 Ts
@Input() field: any = {};
@Input() form: FormGroup;
@Output() sendDataToParent = new EventEmitter<string>();
// form: FormGroup;
itemLength: any;
items: any;
elemData = {}
row = [];
isDisabled : boolean = false;
childs: FormArray;
formNewStatus : boolean = true;
// childs: any;
ngOnInit() {
this.items = this.field.items;
this.itemLength = this.items.length
this.items.forEach(element => {
const elemId =element.id;
this.elemData[elemId] = ''
});
console.log("from control form=====>",this.form)
this.row.push(this.elemData);
}
addTable() {
const frmArr = this.form.get('childs') as FormArray;
this.sendDataToParent.emit("add");
this.row.push(this.elemData)
console.log("this.row===>",this.row)
}
deleteRow(x){
this.row.splice(x, 1 );
}
deleteAllRow(){
console.log("came here")
this.row = [];
}
在父组件中,我添加了 formarray 的代码以添加更多
onFormControlValueChange(parentField:any){
if(parentField === 'add'){
this.childs = this.form.get('childs') as FormArray;
this.childs.push(this.fb.group(this.createItem()));
}
console.log("this form====>", this.form)
}
谁能帮我解决这个问题?提前致谢!:)
解决方案
噗,这是一个大而复杂的代码改进,以允许 fromGroups 和 formArray,但我们将尝试。
首先我们要改变一些 DynamicFormBuilderComponent
我们将创建一个函数 getForm 可以递归创建 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;
}
}
在我们创建对象fields
并最终将对象字段添加到 formGroup 之前,请参阅这一点。使用这个函数,我们直接创建了一个空的 formGroup,我们使用group.addControl
. 这允许我们在必要时调用递归函数。
所以,在 ngOnInit 我们做
ngOnInit() {
this.getForm(this.form, this.fields);
}
看到我们可以使用模板变量从父级访问表单
<dynamic-form-builder #dynamic ..>
{{dynamic.form?.value|json}}
以及我如何确定表单具有“精确”模型 - 在我们有一个带有 {"fields:"exactModel"} 的对象之前 -
我们需要一个辅助函数来创建 FormArray 的 formGroup
addGroupArray(fields:any[])
{
const group:FormGroup=new FormGroup({})
fields.forEach(x=>{
group.addControl(x.name,new FormControl(null,x.required?Validators.required:null))
})
return group
}
我们的 FieldBuilderComponent 会考虑新的两种类型的字段:“数组”和“组”。我把它放在一个字段集中
<!--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>
我使用了两个辅助函数,它们只返回“强制转换”的 formGroup 和 formArray
getFormGroup(field:string)
{
return this.form.get(field) as FormGroup
}
getFormArray(field:string)
{
return this.form.get(field) as FormArray
}
看看我们如何在内部调用自己的组件。我需要添加一个“属性” noLabel 以不显示标签,以防我们管理 FormArrayLabel
//in constructor
constructor(@Attribute('noLabel') noLabel) {
this.noLabel=noLabel || false;
}
并使用
<label *ngIf="!noLabel" ....></label>
我需要addGroupArray
在这个组件中再次重复该功能(我无法想象另一种方式)
好吧,唯一要考虑的是如何将值潜水到formArray,看到数组的字段是这样的:
{
type: "array",
name: "arrayName",
value:[{firstName:"array",lastName:"array lastName"}],
children: [
{
type: "text",
name: "firstName",
label: "First Name",
required: true
},
{
type: "text",
name: "lastName",
label: "Last Name",
required: true
}
]
},
通常这是没有保修的stackblitz
注意:选项的原子应该是
<div [formGroup]="form">
<div class="form-check" *ngFor="let opt of field.options">
<label class="form-check-label">
<input [formControlName]="field.name" class="form-check-input" type="radio" [value]="opt.key" >
{{opt.label}}
</label>
</div>
</div>
更新真的,我从不喜欢在具有真/假值的数组中“拆分”一系列复选框。更自然的是,该值是,例如“c,f”,并选中复选框“烹饪”和“钓鱼”。
[ngModel]
好吧,首先是使用and更改“原子复选框” (ngModelChange)
。首先我们要创建两个辅助函数:
//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);
}
}
好了,现在我们可以使用我们的 [ngModel] 和 (ngModelChange)。我们需要对 Angular 说这是一个“独立的”
<div [formGroup]="form">
<div *ngFor="let opt of field.options" class="form-check form-check">
<label class="form-check-label">
<input
[ngModel]="value && value.indexOf(opt.key) >= 0"
(ngModelChange)="change($event, opt.key)"
[ngModelOptions]="{ standalone: true }"
class="form-check-input"
type="checkbox"
id="inlineCheckbox1"
value="option1"
/>
{{ opt.label }}</label>
</div>
</div>
所以,我们去掉函数getForm中“复选框”的大小写
getForm(group: FormGroup, fields: any[]) {
for (let f of fields) {
switch (f.type) {
case "group":
...
break;
case "array":
....
break;
default:
...
break;
}
}
}
推荐阅读
- mule - Mule 4 与 Weblogic 队列的连接
- c# - 如何修复在 C# 中选择按邮政编码分组的客户订单的 get 方法
- r - 在 mlr 包中调整而不重新采样(聚类)
- bash - 创建脚本以从 csv 文件复制 Web
- java - Thymeleaf 输入表单绑定
- maven - 无法为 Maven 项目创建詹金斯工作
- wordpress - 如何在 WORDPRESS 上添加自定义管理菜单?
- wordpress - 如何将另一个域地址添加到 wordpress 网站?
- docker - 这个 Makefile 有什么问题?需要运行两次才能工作
- python - 获取熊猫数据框中每一行的最高日期值