嵌套的自定义 FormArray 组件不与具有 FormArrayName 的子表单绑定
nested custom FormArray component doesn't bind with child form with FormArrayName
我尝试使用 CVA 有 2 个嵌套表单。问题是当我将它绑定到 formControl 时,第二个 from 没有用数据初始化。
我有 MAIN-FORM:
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-FORM
我为每个 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,
});
主窗体 <--to--> app-cva-form-array 绑定由于某种原因不起作用。
这些表格的想法来自kara's talk on angulaconnect.
here are her slides.
请帮忙!
您需要将更新后的表单数据从子组件传递给父组件。我已经使用 this.form.valueChanges()
方法来检测更改,然后将表单值发送到父组件。
Parent Component:
HTML代码:
<app-cva-form-array formArrayName="routes" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
TS代码:
public onFormChange(form): void {
this.requestForm = form;
}
Child Component:
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();
}
我认为这里的问题是 formArrayName
不是 NG_VALUE_ACCESSOR/DefaultValueAccessor
的输入。
另请注意:
Her examples are static parent->multiple children
... meaning 1 to many and not dynamic. You are attempting static
parent
to many dynamic child->grandChild
relationships built from formArray
, then trying to dynamically link grandChild
form to parent formArrayIndex
that was passed through the child
to the grandChild
. Your stackblitz deviates from the structure she is teaching, and certainly introduces some new challenges not covered in the lecture.
探索如何在 parent
级别迭代 FormArray
并从该循环内实例化您的 child->grandchild
关系可能是一个可能的解决方案,这样您就不会通过整个数组向下,只有 formGroup
会适用。
<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
...
This is the reason formArrayName
will not work in main-form
<--> cva-form-array
however, formControl
will work for
child-child to child level
, as you are passing a singular
formControl
into your app-cva-form
, from your app-cva-form-array
via the *ngFor
loop.
<mat-card *ngFor="let route of getForm.controls;
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
我相信这里要理解的关键是 formArray
只是其 children 的一个组织容器...如果没有来自额外的逻辑。
There currently doesn't appear to be the necessary functionality to
accept formArray
as an input, iterate/dynamically manage the array,
and link changes back to the parent formArray
.
当您使用 "custom Form Control" 时,您需要考虑将表单控件(不是 FormArray,不是 FormGroup)提供给 cursom 表单控件。 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 是
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-form
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-bootstrapDatePicker存储一个对象{年:..月:..,日..},mat-multiselect存储一个数组,...)
Update 当然,我们可以将来自服务或类似服务的数据提供给我们的控件。我们唯一必须考虑的是我们如何提供数据。通常我喜欢制作一个函数来接收数据或 null 和 return 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);
stackblitz 更新
我尝试使用 CVA 有 2 个嵌套表单。问题是当我将它绑定到 formControl 时,第二个 from 没有用数据初始化。
我有 MAIN-FORM:
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-FORM 我为每个 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,
});
主窗体 <--to--> app-cva-form-array 绑定由于某种原因不起作用。
这些表格的想法来自kara's talk on angulaconnect. here are her slides.
请帮忙!
您需要将更新后的表单数据从子组件传递给父组件。我已经使用 this.form.valueChanges()
方法来检测更改,然后将表单值发送到父组件。
Parent Component:
HTML代码:
<app-cva-form-array formArrayName="routes" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
TS代码:
public onFormChange(form): void {
this.requestForm = form;
}
Child Component:
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();
}
我认为这里的问题是 formArrayName
不是 NG_VALUE_ACCESSOR/DefaultValueAccessor
的输入。
另请注意:
Her examples are static
parent->multiple children
... meaning 1 to many and not dynamic. You are attempting staticparent
to many dynamicchild->grandChild
relationships built fromformArray
, then trying to dynamically linkgrandChild
form to parentformArrayIndex
that was passed through thechild
to thegrandChild
. Your stackblitz deviates from the structure she is teaching, and certainly introduces some new challenges not covered in the lecture.
探索如何在 parent
级别迭代 FormArray
并从该循环内实例化您的 child->grandchild
关系可能是一个可能的解决方案,这样您就不会通过整个数组向下,只有 formGroup
会适用。
<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
...
This is the reason
formArrayName
will not work inmain-form <--> cva-form-array
however,formControl
will work forchild-child to child level
, as you are passing a singularformControl
into yourapp-cva-form
, from yourapp-cva-form-array
via the*ngFor
loop.
<mat-card *ngFor="let route of getForm.controls;
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
我相信这里要理解的关键是 formArray
只是其 children 的一个组织容器...如果没有来自额外的逻辑。
There currently doesn't appear to be the necessary functionality to accept
formArray
as an input, iterate/dynamically manage the array, and link changes back to the parentformArray
.
当您使用 "custom Form Control" 时,您需要考虑将表单控件(不是 FormArray,不是 FormGroup)提供给 cursom 表单控件。 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 是
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-form
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-bootstrapDatePicker存储一个对象{年:..月:..,日..},mat-multiselect存储一个数组,...)
Update 当然,我们可以将来自服务或类似服务的数据提供给我们的控件。我们唯一必须考虑的是我们如何提供数据。通常我喜欢制作一个函数来接收数据或 null 和 return 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);
stackblitz 更新