Angular中ngOnInit触发ExpressionChangedAfterItHasBeenCheckedError求助
我之前也碰到过一模一样的问题,其实根源是Angular变更检测的时机冲突——你的代码在Angular已经完成一轮变更检测之后,同步修改了组件的属性,触发了Angular的变更检测校验机制,所以抛出了这个错误。
先说说你当前代码的小问题
你的拦截器在发起请求时同步调用openProgressbar.next(true),而组件的ngOnInit会在Angular初始化变更检测周期内订阅这个Subject,订阅的回调会立即同步修改alertMessageNavbarStatus。这时候Angular已经完成了当前周期的属性检查,发现属性值在检测后发生了变化,就会抛出那个错误。另外还有个细节:你在订阅回调里不管czyOtworzyc的值,直接把alertMessageNavbarStatus设为true,这样加载条永远不会关闭,得改成this.alertMessageNavbarStatus = czyOtworzyc才行。
推荐的解决方案
方案1:使用async管道(最优雅,推荐)
手动订阅容易踩变更检测的坑,用Angular的async管道可以让框架自动处理订阅和变更检测时机,还能避免内存泄漏。
- 在组件类里定义一个可观察对象:
progressbarStatus$ = this.dataStorageService.openProgressbar;
- 在模板里直接用
async管道绑定:
<!-- 假设你的加载条是这个元素 --> <div *ngIf="progressbarStatus$ | async" class="loading-overlay"> 加载中... </div>
这样Angular会自动管理订阅,并且在合适的时机触发变更检测,完全不会触发那个错误。
方案2:手动订阅时,把变更放到下一个周期
如果一定要手动订阅,可以用NgZone.run()或者setTimeout把属性变更推迟到下一个变更检测周期:
用NgZone的方式:
import { NgZone } from '@angular/core'; // 组件构造函数注入NgZone constructor(private dataStorageService: DataStorageService, private ngZone: NgZone) {} ngOnInit() { this.dataStorageService.openProgressbar.subscribe(czyOtworzyc => { // 用NgZone.run把变更放到Angular的变更检测队列里 this.ngZone.run(() => { this.alertMessageNavbarStatus = czyOtworzyc; }); }); }
用setTimeout的方式:
ngOnInit() { this.dataStorageService.openProgressbar.subscribe(czyOtworzyc => { // setTimeout会把代码放到下一个宏任务,等当前变更检测完成后再执行 setTimeout(() => { this.alertMessageNavbarStatus = czyOtworzyc; }, 0); }); }
额外的小优化
如果你的项目用的是RxJS 6及以上版本,拦截器里的do和catch已经被废弃了,建议替换成tap和catchError,Observable.throw换成throwError,同时别忘了请求失败时也要关闭加载条:
import { tap, catchError } from 'rxjs/operators'; import { throwError } from 'rxjs'; intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const dataStorageService = this.injector.get(DataStorageService); dataStorageService.openProgressbar.next(true); return next .handle(req) .pipe( tap(evt => { if (evt instanceof HttpResponse) { dataStorageService.openProgressbar.next(false); } }), catchError(err => { // 请求失败时也要关闭加载条,避免一直显示 dataStorageService.openProgressbar.next(false); return throwError(() => err); }) ); }
内容的提问来源于stack exchange,提问作者Pawel

