嵌套的自定义 FormArray 组件不与具有 FormArrayName 的子表单绑定
Posted
技术标签:
【中文标题】嵌套的自定义 FormArray 组件不与具有 FormArrayName 的子表单绑定【英文标题】:nested custom FormArray component doesn't bind with child form with FormArrayName 【发布时间】:2019-08-29 15:51:39 【问题描述】:我尝试使用 CVA 创建 2 个嵌套表单。问题是当我将第二个 from 绑定到 formControl 时,它没有用数据初始化。
Stackblitz
我有主窗体:
this.requestForm = this.fb.group(
garageId: 0,
routes: new FormArray([
new FormGroup(
addressPointId: new FormControl,
municipalityId: new FormControl,
regionId: new FormControl,
rvId: new FormControl,
sequenceNumber: new FormControl,
settlementId: new FormControl,
regionName: new FormControl,
municipalityName: new FormControl,
settlementName: new FormControl,
description: new FormControl,
)
]),
endDateTime: 0,
);
在主表单 html 中,我将路由绑定到 formArrayName。
<app-cva-form-array formArrayName="routes"></app-cva-form-array>
组件CVA-FORM-ARRAY有。
form = new FormArray([
new FormGroup(
addressPointId: new FormControl,
municipalityId: new FormControl,
regionId: new FormControl,
rvId: new FormControl,
sequenceNumber: new FormControl,
settlementId: new FormControl,
regionName: new FormControl,
municipalityName: new FormControl,
settlementName: new FormControl,
description: new FormControl,
)
]);
这里的一切都很好。我将数组中的每个 formGroup 绑定到子组件 CVA-FORM。
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
CVA 格式 对于每个 formGroup,我创建了单独的组件,以防我想使用组件本身而不是整个数组。
form: FormGroup = new FormGroup(
regionName: new FormControl,
regionId: new FormControl,
municipalityName: new FormControl,
municipalityId: new FormControl,
sequenceNumber: new FormControl,
settlementName: new FormControl,
settlementId: new FormControl,
addressPointId: new FormControl,
description: new FormControl,
rvId: new FormControl,
);
main-form app-cva-form-array 绑定由于某种原因不起作用。
这些表格的想法来自kara's talk on angulaconnect. here are her slides.
请帮忙!
【问题讨论】:
不清楚你是在问背景表格吗? 是的。如果你看一下jsons。您会看到主窗体(背景窗体)与其他窗体不同步。 看看posetd的答案 【参考方案1】:当您使用“自定义表单控件”时,您需要考虑使用表单控件(不是 FormArray,不是 FormGroup)来提供粗略的表单控件。 FormControl 有一个数组或一个对象作为值,但您不必对此感到困惑。(*)
你可以在stackblitz工作中看到
那是你的表格是这样的
//in main.form
this.requestForm = new FormGroup(
garageId: new FormControl(0),
routes: new FormControl(routes), //<--routes will be an array of object
endDateTime: new FormControl(0)
)
//in cva-form-array
this.form=new FormArray([new FormControl(...)]); //<-this.form is a
//formArray of FormControls NOT of formGroup
//finally in your cva-form
this.form=new FormGroup();
this.form=formGroup(
addressPointId: new FormControl(),
municipalityId: new FormControl(),
...
)
我创建了一个 const 来导出为简单的代码。我的 const 出口是
export const dataI =
addressPointId: "",
municipalityId: "",
regionId: "",
rvId: "",
sequenceNumber: "",
settlementId: "",
regionName: "",
municipalityName: "",
settlementName: "",
description: "",
所以,在 mainForm 中我们有
ngOnInit()
let routes:any[]=[];
routes.push(...dataI);
this.requestForm = new FormGroup(
garageId: new FormControl(0),
routes: new FormControl(routes),
endDateTime: new FormControl(0)
)
<mat-card [formGroup]="requestForm" style="background: #8E8D8A">
<app-cva-form-array formControlName="routes"></app-cva-form-array>
</mat-card>
在 cvc-form 数组中,我们给出值时创建 formArray
writeValue(v: any)
this.form=new FormArray([]);
for (let value of v)
this.form.push(new FormControl(value))
this.form.valueChanges.subscribe(res=>
if (this.onChange)
this.onChange(this.form.value)
)
<form [formGroup]="form" >
<mat-card *ngFor="let route of form.controls;
let routeIndex = index; let routeLast = last;">
<button (click)="deleteRoute(routeIndex)">
cancel
</button>
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
</form>
最后是 cva 形式
writeValue(v: any)
this.form=new FormGroup();
Object.keys(dataI).forEach(x=>
this.form.addControl(x,new FormControl())
)
this.form.setValue(v, emitEvent: false );
this.form.valueChanges.subscribe(res=>
if (this.onChanged)
this.onChanged(this.form.value)
)
<div [formGroup]="form">
<mat-form-field class="locationDate">
<input formControlName="regionName">
<mat-autocomplete #region="matAutocomplete"
(optionSelected)="selectedLocation($event)">
<mat-option *ngFor="let region of regions"
[value]="region">
region.regionName
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="locationDate">
<input formControlName="municipalityName"
[matAutocomplete]="municipality"
(blur)="onTouched()"
[readonly]="checked || this.form.value.regionId < 1">
....
</form>
(*) 是的,我们习惯于看到 FormControl 的值是字符串或数字,但没有人禁止我们该值是对象或数组(例如,ng-bootstrap DatePicker 存储一个object year: .. month: .., day ..,mat-multiselect 存储一个数组,...)
更新当然,我们可以用来自服务或类似的数据来提供我们的控件。我们唯一必须考虑的是我们如何提供数据。通常我喜欢创建一个接收数据或 null 并返回 FormControl 的函数
getForm(data: any): FormGroup
data = data || as IData;
return new FormGroup(
garageId: new FormControl(data.garageId),
routes: new FormControl(data.routes),
endDateTime: new FormControl(data.endDateTime)
)
IData 是一个接口
export interface IData
garageId: number;
routes: IDetail[];
endDateTime: any
和IDetail另一个界面
export interface IDetail
addressPointId: string;
...
description: string;
然后我们可以有一个复杂的数据,比如(对不起大对象)
let data =
garageId: 1,
routes: [
addressPointId: "adress",
municipalityId: "municipallyty",
regionId: "regionId",
rvId: "rvId",
sequenceNumber: "sequenceNumber",
settlementId: "settlementId",
regionName: "regionName",
municipalityName: "municipalityName",
settlementName: "settlementName",
description: "description",
,
addressPointId: "another adress",
municipalityId: "another municipallyty",
regionId: "another regionId",
rvId: "another rvId",
sequenceNumber: "another sequenceNumber",
settlementId: "another settlementId",
regionName: "another regionName",
municipalityName: "another municipalityName",
settlementName: "another settlementName",
description: "another description",
],
endDateTime: new Date()
那么只需要make
this.requestForm = this.getForm(data);
如果更新了堆栈闪电战
【讨论】:
感谢您的回答!我可以使用接口而不是常量吗?我不希望表单数组绑定到主表单。还是您认为我应该将 const 放在不同的文件中? 你好,我试图将解决方案放在我的项目中。我尝试加载已经存在的初始数据。我遵循与您相同的代码。但是我注意到,当我使用数据进行初始化时,它不会传输给子节点,当我尝试添加另一个路由时,它会将索引放在数组对象的前面。像这样“路线”:“0”:“regionId”:“”...“municipalityId”:“”。这也可能是初始化的问题。你知道有什么解决办法吗? @Vato,我更新了答案和stackblitz,希望对您有所帮助。关于使用接口和常量,我试过了,但没有结果:(,当然接口和常量可以在主窗体之外 @Eliseo,谢谢它很好用。但是一旦添加地址,它仍然会在数组前面添加索引。它使数组 0: 1: ... 等在添加数组之前看起来很好 [ , , ] 为了避免 ExpressionChanged... 它的 util 有时使用setTimeOut(()=>...your code..)
【参考方案2】:
我认为这里的问题是formArrayName
不是NG_VALUE_ACCESSOR/DefaultValueAccessor
的输入。
另请注意:
她的例子是静态的
parent->multiple children
...意思是一对多而不是动态的。您正在尝试静态parent
到从formArray
构建的许多动态child->grandChild
关系,然后尝试将grandChild
表单动态链接到通过child
传递到grandChild
的父formArrayIndex
。您的 stackblitz 偏离了她所教的结构,并且肯定会引入一些讲座中未涉及的新挑战。
探索如何在parent
级别上迭代FormArray
并从该循环中实例化您的child->grandchild
关系可能是一个可能的解决方案,这样您就不会向下传递整个数组,只有@987654335 @ 那将适用。
<h1>MAIN FORM</h1>
requestForm.value | json
<div *ngFor="let route of requestForm.get('routes').controls">
<app-cva-form-array formControl="route" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
</div>
选择器
输入:not([type=checkbox])[formControlName] textarea[formControlName] 输入:not([type=checkbox])[formControl] textarea[formControl] 输入:not([type=checkbox])[ngModel] textarea[ngModel] [ngDefaultControl]https://angular.io/api/forms/DefaultValueAccessor#selectors
您唯一的输入选项是formControlName
、formControl
、ngModel
和ngDefaultControl
...
这就是
formArrayName
不在main-form <--> cva-form-array
中工作的原因,但是formControl
将工作在child-child to child level
,因为您传递的是单数formControl
进入你的app-cva-form
,从你的app-cva-form-array
通过*ngFor
循环。
<mat-card *ngFor="let route of getForm.controls;
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
我相信这里要理解的关键是formArray
只是其子级的组织容器......在这种情况下,如果没有其他逻辑的帮助,它不会做你想要的事情。
目前似乎没有必要的功能 接受
formArray
作为输入,迭代/动态管理数组, 并将链接更改回父formArray
。
【讨论】:
【参考方案3】:您需要将更新后的表单数据从子组件传递给父组件。我已经使用this.form.valueChanges()
方法来检测更改,然后将 Form 值发送到父组件。
父组件:
HTML 代码:
<app-cva-form-array formArrayName="routes" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
TS 代码:
public onFormChange(form): void
this.requestForm = form;
子组件:
HTML 代码:
No change:)
TS 代码:
@Output() onChildFormValueChange: EventEmitter<any> = new EventEmitter<any>();
registerEvent()
this.form.valueChanges.subscribe(() =>
this.onFormValueChange()
);
public onFormValueChange(): void
this.onChildFormValueChange.emit(this.form);
并在构造函数中调用registerEvent
方法,如:
constructor()
this.registerEvent();
Working_Stackblitz
【讨论】:
这仅提供一种方式绑定。虽然我知道如何做两种方式,但它仍然不是我想要的。如果您检查 cva-form-array 如何绑定到 cva-form,我想为 main-form cva-form-array 实现类似的绑定。使用 cva 的目的是为了简化代码而不是使用额外的函数。 我在问题底部添加了关于代码来源的外部资源。 @Vato 不,这是双向绑定 如果您更改主表单,则 cva-form-array 不会更改。如果你写 regionName: new FormControl('asdfa'),在 main-form 中它会让子组件 regionnames 为空。 stackblitz.com/edit/angular-b6nm8c 在这里,如果您从父级添加数组,它不会只绑定到子级,反之亦然。但无论如何,正如我所说,使用 cva 不需要任何功能。以上是关于嵌套的自定义 FormArray 组件不与具有 FormArrayName 的子表单绑定的主要内容,如果未能解决你的问题,请参考以下文章
为具有嵌套标签的自定义标签编写 jekyll 插件时出现问题