首先感谢@AJT_82的吹捧建议!
我已经在研究一个更简单的代码重现错误的示例,可以在 stackblitz.com/edit/angular-42gobh 有问题的行被注释,以便您可以检查正确的结果应该是什么。只需取消注释<div [formGroup]="i"></div>
让一切都崩溃。
基本上,我有一个为我的组件构建表单的服务,HTML 文件使用 Angular Material。当手风琴用于 formArray 时,应用程序完全崩溃,并且无法正确分配 formGroup:
customer-edit.service.ts:
import { Injectable } from '@angular/core';
import { FormGroup, FormBuilder, FormArray, Validators } from '@angular/forms';
@Injectable({
providedIn: 'root'
})
export class CustomerEditService {
private cusForm: FormGroup = this.fb.group({
thirdParty: this.fb.group({
name: this.fb.control(null),
vat: this.fb.control(null),
corpoPhone: this.fb.control(null),
corpoMail: this.fb.control(null),
corpoWeb: this.fb.control(null),
activityNumber: this.fb.control(null),
addresses: this.fb.array([]),
contacts: this.fb.array([])
}),
docRefs: this.fb.group({}),
commentsArr: this.fb.group({})
});
constructor(
private fb: FormBuilder
) { }
// **** EMPTY FORMS GETTERS ****
getAddressForm(address?: any) {
const addressForm: FormGroup = this.fb.group({
street: this.fb.control(null),
streetcomp: this.fb.control(null),
streetcomp2: this.fb.control(null),
city: this.fb.control(null),
cp: this.fb.control(null),
state: this.fb.control(null),
country: this.fb.control(null),
main: this.fb.control(null)
});
if (address) {
addressForm.setValue(address);
}
return addressForm;
}
getFilledThirdPartyForm(thirdParty?: any) {
const thirdPartyForm: FormGroup = this.fb.group({
name: this.fb.control(null, Validators.required),
vat: this.fb.control(null, Validators.required),
corpoPhone: this.fb.control(null, Validators.required),
corpoMail: this.fb.control(null, Validators.required),
corpoWeb: this.fb.control(null, Validators.required),
activityNumber: this.fb.control(null),
});
if (thirdParty) {
Object.keys(thirdParty).map(
el => {
if (Object.keys(thirdPartyForm.controls).indexOf(el) !== -1 && el !== 'addresses') {
thirdPartyForm.get(el).setValue(thirdParty[el]);
}
});
}
return thirdPartyForm;
}
}
这是构建表单的组件的 TS 文件。表单是基于"现实生活中的JSON对象(第三方("构建的,该对象通过HTTP请求来自数据库:
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
import { CustomerEditService } from '../customer-edit.service';
@Component({
selector: 'app-basic-edit',
templateUrl: './test.component.html'
})
export class testComponent implements OnInit {
thirdParty: any =
{
"addresses": [
{
"street": "AVENIDA ESTADOS UNIDOS, 141",
"streetcomp": "",
"streetcomp2": "",
"city": "SAN BARTOLOME DE TIRAJANA ",
"cp": "35290",
"state": "PALMAS (LAS)",
"country": "spain",
"main": true
},
{
"street": "OTRA DIRECCION DUMMY",
"streetcomp": "",
"streetcomp2": "",
"city": "MADRID",
"cp": "280007",
"state": "MADRID",
"country": "spain",
"main": false
}
],
"contacts": [
{
"_id": "5cf0f6f2a3e9cf847c5861af",
"title": "Mrs.",
"role": "CFO",
"firstName": "John",
"lastName": "Doe",
"phone": "912345654",
"mobile": "673369900",
"thirdParty_id": "5cf0f6d0a3e9cf847c5861aa",
"addresses": [
{
"street": "AVENIDA ESTADOS UNIDOS , 141",
"streetcomp1": "TUNTE",
"streetcomp2": "",
"cp": "35290",
"city": "SAN BARTOLOME DE TIRAJANA ",
"state": "PALMAS (LAS)"
}
],
"email": "jdoe@ketchup.com",
"auditTrail": {
"creation": {
"user_id": "1",
"creationDate": "1559213796974"
},
"modification": [
{
"user_id": "1",
"modifDate": "1559213833358"
}
]
}
}
]
};
thirdPartyForm: FormGroup;
constructor(
private fb: FormBuilder,
private cusEditService: CustomerEditService
) {
}
ngOnInit() {
this.thirdPartyForm = this.cusEditService.getFilledThirdPartyForm(this.thirdParty);
const addresses: any[] = this.thirdParty.addresses;
const addressesFormArr: FormArray = new FormArray([]);
addresses.forEach(
address => {
const currAddressForm: FormGroup = this.cusEditService.getAddressForm(address);
addressesFormArr.push(currAddressForm);
});
this.thirdPartyForm.setControl(
'addresses',
addressesFormArr
);
console.log(this.thirdPartyForm.get('addresses'));
}
onSubmit() {
console.log('Submitted');
}
}
这是 HTML:
<h1>Addresses Test</h1>
<form [formGroup]="thirdPartyForm" (ngSubmit)="onSubmit()">
<div formArrayName="addresses">
<mat-accordion>
<mat-expansion-panel *ngFor="let address of thirdPartyForm.get('addresses').value; index as i">
<mat-expansion-panel-header>
{{ address.city }}
</mat-expansion-panel-header>
<div [formGroupName]="i"> <!-- UNCOMMENTING THIS LINE MAKES EVERYTHING CRASH -->
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</form>
希望我能告诉你确切的原因,但解决方案是将您的迭代更改为控件而不是值以使其工作:
<mat-expansion-panel *ngFor="let address of thirdPartyForm.get('addresses').controls; index as i" >
<mat-expansion-panel-header>
{{ address.get('city').value }}
</mat-expansion-panel-header>
<div [formGroupName]="i">
<input formControlName="city">
</div>
</mat-expansion-panel>
我在这里最好的猜测是,通过访问值进行迭代并尝试在其中嵌套表单组名称,您已经创建了某种循环,其中内容不断重新呈现,因为访问 formGroupName 指令会导致值重新评估,而控件不会,因为它是静态属性。窗体控件中的值实际上是引擎盖下的获取器,因此可以重新评估。函数迭代是不纯的,因此它们不断被重新评估,因此,在 ngFor 中,内容不断被重新呈现(缺少 trackBy 子句(。
我注意到迭代值似乎在没有手风琴的情况下工作,所以我猜这也与这里的子组件方面有关,不断的重新渲染会导致内存不足并崩溃。 可能值得在他们的 github 上注册这个,因为他们至少应该记录你不应该尝试这样做。
我继续添加了一个测试指令,该指令将其构造函数记录到一些普通的div中,在那里我按值迭代,其中有和没有表单组名指令。 我发现,其中 n = 表单数组的长度,该指令将实例化 n*2 次,而内部没有 formGroupName 指令。 但是使用表单组名称,它实例化了 n^2 + 2n 次。因此,可以肯定的是,迭代值会导致它计算两次,然后出于某种原因添加该指令会导致它再次评估顶部数组中的每个组,从而以指数方式呈现内容。 相反,使用控件进行迭代会导致指令按预期实例化 n 次。
仍然不完全确定这一切的原因,但请确定发生这种情况并且是导致崩溃的原因。
演示闪电战:https://stackblitz.com/edit/angular-fgqytp?file=src/app/test-component/test.component.html
不相关的旁注,您可以像这样使用表单构建器:
const thirdPartyForm: FormGroup = this.fb.group({
name: [null, Validators.required],
vat: [null, Validators.required],
corpoPhone: [null, Validators.required],
corpoMail: [null, Validators.required],
corpoWeb: [null, Validators.required],
activityNumber: null
})
表单生成器中的"叶"值被假定为控件,并且它接受该数组语法以根据需要添加验证器。 这是使用表单构建器与仅执行new FormGroup({key: new FormControl(null)})
的主要好处