Angular中使用BehaviorSubject触发ExpressionChangedAfterItHasBeenCheckedError如何解决
错误原因
这个场景下报错是因为Angular单次变更检测周期内,父组件(工具栏所属组件)先完成了视图值校验,随后子组件在同一个周期内同步触发了BehaviorSubject的更新,导致工具栏绑定的面包屑值发生了变更,和之前校验的值不一致就抛出该错误。另外你在模板中直接调用messageService.getBreadcrumbs()方法也会加剧这个问题:每次变更检测都会执行该方法返回新的Observable实例,async管道需要重复订阅,更容易出现值波动。
可行解决思路
调整订阅写法,避免模板内调用方法返回Observable
把Observable提前在组件类中定义为公共属性,模板直接绑定该属性即可,避免每次变更检测生成新的流实例。
组件类示例:import { Component, ChangeDetectionStrategy } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'app-toolbar', template: `<breadcrumbs [crumbs]="breadcrumbs$ | async"></breadcrumbs>`, // 可选配合OnPush变更检测策略,进一步降低冲突概率还能提升性能 changeDetection: ChangeDetectionStrategy.OnPush }) export class ToolbarComponent { // 提前定义流,不要在模板中调用方法返回 public breadcrumbs$ = this.messageService.getBreadcrumbs(); constructor(private messageService: MessageService) {} }延迟子组件的更新操作到下一个任务周期
如果子组件是在ngAfterViewInit等晚于父组件变更检测的钩子中更新面包屑,把更新操作包在微任务/宏任务中,放到下一个变更检测周期执行即可。
子组件更新示例:import { Component, AfterViewInit } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'app-child', template: `` }) export class ChildComponent implements AfterViewInit { constructor(private messageService: MessageService) {} ngAfterViewInit() { // 用Promise.resolve放到微任务队列,或者用setTimeout放到宏任务队列都可以 Promise.resolve().then(() => { this.messageService.updateBreadcrumbs('Project'); }); } }手动触发变更检测同步状态
如果不想延迟更新,可以在更新完面包屑值之后,注入ChangeDetectorRef手动触发一次变更检测,同步视图和数据的状态。
示例:import { Component, ChangeDetectorRef } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'app-child', template: `` }) export class ChildComponent { constructor( private messageService: MessageService, private cdr: ChangeDetectorRef ) {} updateCrumbs() { this.messageService.updateBreadcrumbs('Project'); // 手动触发变更检测,同步状态 this.cdr.detectChanges(); } }
内容的提问来源于stack exchange,提问作者HelloWorld
相关产品推荐
相关产品推荐

