Angular表单调用子组件出现ExpressionChangedAfterItHasBeenCheckedError如何解决
错误根因
该错误是Angular开发模式下的特有校验报错,触发原因是当前变更检测周期内,视图渲染完成后绑定的表达式值又发生了修改,你的场景下有两个核心触发点:
- 子组件
ngOnInit生命周期中重写了父组件传入的myForm输入属性,导致父子组件的表单状态在变更检测周期内不一致 - 父组件异步加载用户数据执行
patchValue的时机,与子组件异步加载性别选项、手动触发变更检测的时机存在时序冲突
修复方案
1. 移除父子组件冗余的FormGroup传参
子组件已经通过ControlContainer注入了父表单的上下文,不需要再通过@Input传递表单实例,同时避免重写输入属性触发状态变更:
// 子组件代码修改 export class GenderComponent implements OnInit { public errorMessage: string = ''; @Input() public genders: Gender[]; @Input() public selectedValue?: string =""; public myForm: FormGroup; // 删除原有的@Input()装饰器,改为内部普通属性 constructor(private repository: RepositoryService, private controlContainer: ControlContainer, private errorHandler: ErrorHandlerService, private cd: ChangeDetectorRef) { } ngOnInit(): void { this.myForm = <FormGroup>this.controlContainer.control; this.getGenders(); } getGenders = () => { let apiGender: string = environment.apiAddress + 'Gender'; this.repository.getData(apiGender) .subscribe(response => { this.genders = response as Gender[]; }, (error) => { this.errorHandler.handleError(error); this.errorMessage = this.errorHandler.errorMessage; }) } }
同步修改父组件的调用代码,删除[formGroup]绑定:
<app-gender name="gender" id="gender" [selectedValue]="user?.gender?.description"> <em *ngIf="validateControl('gender') && hasError('gender', 'required')">Sélectionnez un genre</em> </app-gender>
2. 调整父组件patchValue的执行时机
将表单赋值操作放到微任务队列,避开当前正在执行的变更检测周期:
public loadUserDetails = () => { let id: string = this.activeRoute.snapshot.params['id']; let apiUser: string = environment.apiAddress + 'UserAccount' + '/' + id; this.repository.getData(apiUser) .subscribe(response => { this.user = response as UserAccount; // 用setTimeout将赋值操作延迟到下一个变更检测周期执行 setTimeout(() => { this.myForm.patchValue({ firstname: (<UserAccount>response).firstName, lastname: (<UserAccount>response).lastName, mail: (<UserAccount>response).mail, genderId: (<UserAccount>response).genderId, password: (<UserAccount>response).password, username: (<UserAccount>response).userName, login: (<UserAccount>response).login, useraccountTypeId: (<UserAccount>response).useraccountTypeId, }) }) }, (error) => { this.errorHandler.handleError(error); this.errorMessage = this.errorHandler.errorMessage; }) }
3. 优化子组件变更检测触发逻辑
将ngAfterViewInit中的手动变更检测移到异步请求完成后,避免无意义的变更检测触发:
// 子组件代码修改 getGenders = () => { let apiGender: string = environment.apiAddress + 'Gender'; this.repository.getData(apiGender) .subscribe(response => { this.genders = response as Gender[]; this.cd.detectChanges(); // 拿到性别选项数据后再触发变更检测 }, (error) => { this.errorHandler.handleError(error); this.errorMessage = this.errorHandler.errorMessage; }) } ngAfterViewInit(){ // 删除原有的this.cd.detectChanges()调用 }
内容的提问来源于stack exchange,提问作者Paintbox
相关产品推荐
相关产品推荐

