动态嵌套反应形式:ExpressionChangedAfterItHasBeenCheckedError
Posted
技术标签:
【中文标题】动态嵌套反应形式:ExpressionChangedAfterItHasBeenCheckedError【英文标题】:Dynamic nested reactive form: ExpressionChangedAfterItHasBeenCheckedError 【发布时间】:2018-01-21 11:52:39 【问题描述】:我的响应式表单是三个组件级别的深度。父组件创建一个没有任何字段的新表单并将其传递给子组件。
起初外部形式是有效的。稍后,子组件会添加带有验证器(失败)的新表单元素,从而使外部表单无效。
我在控制台中收到 ExpressionChangedAfterItHasBeenCheckedError 错误。我想修正那个错误。
不知何故,这只发生在我添加第三层嵌套时。同样的方法似乎适用于两层嵌套。
Plunker: https://plnkr.co/edit/GymI5CqSACFEvhhz55l1?p=preview
父组件
@Component(
selector: 'app-root',
template: `
myForm.valid: <b>myForm.valid</b>
<form>
<app-subform [myForm]="myForm"></app-subform>
</form>
`
)
export class AppComponent implements OnInit
...
ngOnInit()
this.myForm = this.formBuilder.group();
子组件
@Component(
selector: 'app-subform',
template: `
<app-address-form *ngFor="let addressData of addressesData;"
[addressesForm]="addressesForm">
</app-address-form>
`
)
export class SubformComponent implements OnInit
...
addressesData = [...];
constructor()
ngOnInit()
this.addressesForm = new FormArray([]);
this.myForm.addControl('addresses', this.addressesForm);
子组件
@Component(
selector: 'app-address-form',
template: `
<input [formControl]="addressForm.controls.addressLine1">
<input [formControl]="addressForm.controls.city">
`
)
export class AddressFormComponent implements OnInit
...
ngOnInit()
this.addressForm = this.formBuilder.group(
addressLine1: [
this.addressData.addressLine1,
[ Validators.required ]
],
city: [
this.addressData.city
]
);
this.addressesForm.push(this.addressForm);
【问题讨论】:
查看Everything you need to know about theExpressionChangedAfterItHasBeenCheckedError
error文章
我在过去 24 小时内访问了该页面 5 次 ;)。仍然不确定如何解决它。
你引用的 plunker 没有错误
很奇怪。在禁用所有扩展程序的情况下,我在 Chrome 和 Firefox 中都遇到了错误。在 Plunker 和 localhost 中。添加了截图供参考。
我在使用 plunk 时遇到错误。我删除了地址表单组件中的验证([ Validators.required ]
),错误消失了。不确定这些信息是否有用。
【参考方案1】:
要了解问题需要阅读Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error 文章。
对于您的特定情况,问题是您在AppComponent
中创建一个表单并在DOM 中使用myForm.valid
插值。这意味着 Angular will run create and run updateRenderer function 用于更新 DOM 的 AppComponent
。然后你使用子组件的ngOnInit
生命周期钩子将带有控件的子组添加到此表单:
export class AddressFormComponent implements OnInit
@Input() addressesForm;
@Input() addressData;
ngOnInit()
this.addressForm = this.formBuilder.group(
addressLine1: [
this.addressData.addressLine1,
[ Validators.required ] <-----------
]
this.addressesForm.push(this.addressForm); <--------
控件变得无效,因为您没有提供初始值并且您指定了必需的验证器。因此整个表单变得无效,表达式myForm.valid
的计算结果为false
。但是当 Angular 对AppComponent
运行更改检测时,它的评估结果为true
。这就是错误所说的。
一个可能的解决方法是在开始时将表单标记为无效,因为您计划添加所需的验证器,但 Angular 似乎没有提供这种方法。您最好的选择可能是异步添加控件。事实上,这就是 Angular 在源代码中所做的:
const resolvedPromise = Promise.resolve(null);
export class NgForm extends ControlContainer implements Form
...
addControl(dir: NgModel): void
// adds controls asynchronously using Promise
resolvedPromise.then(() =>
const container = this._findContainer(dir.path);
dir._control = <FormControl>container.registerControl(dir.name, dir.control);
setUpControl(dir.control, dir);
dir.control.updateValueAndValidity(emitEvent: false);
);
所以你的情况是:
const resolvedPromise = Promise.resolve(null);
@Component(
...
export class AddressFormComponent implements OnInit
@Input() addressesForm;
@Input() addressData;
addressForm;
ngOnInit()
this.addressForm = this.formBuilder.group(
addressLine1: [
this.addressData.addressLine1,
[ Validators.required ]
],
city: [
this.addressData.city
]
);
resolvedPromise.then(() =>
this.addressesForm.push(this.addressForm); <-------
)
或者在AppComponent
中使用一些变量来保存表单状态并在模板中使用它:
formIsValid
export class AppComponent implements OnInit
myForm: FormGroup;
formIsValid = false;
constructor(private formBuilder: FormBuilder)
ngOnInit()
this.myForm = this.formBuilder.group();
this.myForm.statusChanges((status)=>
formIsValid = status;
)
【讨论】:
感谢非常详细的回答!欣赏它。 对于存档:第一种方法完美无缺。第二个没用。它只是将相同的错误移至另一个变量。还需要一个 .subscribe 来检查 statusChanges。 我们使用自己的嵌套表单实现,基于模板驱动的表单。但问题是一样的。添加或删除控件时,会出现错误。添加resolvedPromise
,问题就解决了!谢谢@AngularInDepth.com
@DHFW,很高兴我能帮上忙
第二种解决方案(在变量上保持valid
状态)对我有用,但由于某种原因我不得不用setTimeout()
包装它。谢谢【参考方案2】:
import ChangeDetectorRef from '@angular/core';
....
export class SomeComponent
form: FormGroup;
constructor(private fb: FormBuilder,
private ref: ChangeDetectorRef)
this.form = this.fb.group(
myArray: this.fb.array([])
);
get myArray(): FormArray
return this.form.controls.myArray as FormArray;
addGroup(): void
const newGroup = this.fb.group(
prop1: [''],
prop2: ['']
);
this.myArray.push(newGroup);
this.ref.detectChanges();
【讨论】:
这绝对是一个被低估的答案...【参考方案3】:我在 Angular 9 及更高版本的解决方案中遇到了相同的场景和相同的问题。我稍微调整了一下:在没有验证器的情况下同步添加控件并异步添加所需的验证器...因为我立即需要控件,否则我收到错误cannot find formControl ...
。
这是我基于上面接受的答案的解决方案:
const resolvedPromise = Promise.resolve(null);
@Component(
selector: 'password-input',
templateUrl: './password-input.component.html',
styleUrls: ['./password-input.component.css']
)
export class PasswordInputComponent implements OnInit
@Input() parentFormGroup : FormGroup;
@Input() controlName : string = 'password';
@Input() placeholder : string = 'Password';
@Input() label : string = null;
ngOnInit(): void
this.parentFormGroup.addControl(this.controlName, new FormControl(null));
resolvedPromise.then( () =>
this.parentFormGroup.get(this.controlName).setValidators(Validators.required)
this.parentFormGroup.get(this.controlName).updateValueAndValidity();
);
【讨论】:
做了同样的事情,只是用 setTimeout()【参考方案4】:按照 Max Koretskyi 的解决方案,我们可以在 ngOnInit
上使用 async/await 模式。
这是一个例子:
@Component(
selector: 'my-sub-component',
templateUrl: 'my-sub-component.component.html',
)
export class MySubComponent implements OnInit
@Input()
form: FormGroup;
async ngOnInit()
// Avoid ExpressionChangedAfterItHasBeenCheckedError
await Promise.resolve();
this.form.removeControl('config');
this.form.addControl('config', new FormControl("", [Validator.required]));
【讨论】:
以上是关于动态嵌套反应形式:ExpressionChangedAfterItHasBeenCheckedError的主要内容,如果未能解决你的问题,请参考以下文章