I tried to have 2 nested forms using CVA. the problem is the second from isn't initialized with data when I bind it to a formControl.
I have 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,
});
In main-form html I bind routes to with formArrayName.
<app-cva-form-array formArrayName="routes"></app-cva-form-array>
Component CVA-FORM-ARRAY has.
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,
})
]);
Everything from here works just fine. I bind each formGroup in the array to child component CVA-FORM.
<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
CVA-FORM for each formGroup I created separate component in case I want to use component itself not the whole array.
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,
});
the main-form <--to--> app-cva-form-array binding doesn't work for some reason.
The idea of these forms comes from kara's talk on angulaconnect. here are her slides.
help plz!
答案 0 :(得分:2)
当您使用“自定义表单控件”时,需要考虑到您将带有表单控件(不是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 expor是
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存储一个对象{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()
}
然后只需要
this.requestForm = this.getForm(data);
堆叠闪电战(如果更新)
答案 1 :(得分:1)
我认为这里的问题是formArrayName
不是NG_VALUE_ACCESSOR/DefaultValueAccessor
的输入。
也请注意:
她的示例是静态的
parent->multiple children
...意味着1对很多,而不是动态的。您正在尝试静态parent
到由child->grandChild
建立的许多动态formArray
关系,然后尝试将grandChild
形式动态链接到通过formArrayIndex
传递的父child
到grandChild
。您的堆叠突飞猛进偏离了她所教的结构,并且肯定会引入一些讲座中未涵盖的新挑战。
探索如何在FormArray
级别上遍历parent
并从该循环中实例化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>
选择器
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
。
答案 2 :(得分:0)
您需要将更新的表单数据从子组件传递到父组件。我使用了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();
}