Angular - 同一组件中的ExpressionChangedAfterItHasBeenCheckedError

时间:2018-02-19 16:50:18

标签: angular angular-http-interceptors

我有一个简单的 Angular 5 应用程序,其中包含 HttpInterceptor ,可在发出请求时通知服务。组件可以订阅此服务以获取此信息。

此处有错误的完整示例=> https://stackblitz.com/edit/angular-5b86se

在我的 AppComponent 中,我想在我的子组件( HelloComponent )中发出请求时显示加载指示符。

import { Component } from '@angular/core';
import { LoadingIndicatorService } from './loading-indicator.service';

@Component({
  selector: 'my-app',
  template: `<div *ngIf="loading" style="color:red">pretend this is loading indicator</div>
<hello name="{{ name }}"></hello>`
})
export class AppComponent  {
  name = 'Angular 5';
   loading: boolean = false;

   constructor(private loadingIndicatorService: LoadingIndicatorService) { 

    loadingIndicatorService
      .onLoadingChanged
      .subscribe(isLoading => this.loading = isLoading);
  }
}

我收到以下错误:

  

错误:ExpressionChangedAfterItHasBeenCheckedError:Expression有   检查后改变了。上一个值:'false'。当前值:   '真正的'。

如果我是正确的,当子组件更改父组件中的值时,会出现此错误。

但我的子组件( HelloComponent )只执行HttpGet而 HttpInterceptor 正在通过服务更改父组件( AppComponent )有一个可观察的。

不应该更改服务是正确的解决方案吗? 有人可以解释为什么这不起作用?

3 个答案:

答案 0 :(得分:1)

它会抛出一个错误,因为在调用onLoadingChanged时,变量加载会更新。意味着您的初始值已经改变。

我建议您详细了解 here

对此的一般修正是,

    constructor(private loadingIndicatorService: LoadingIndicatorService,private ref: ChangeDetectorRef) { 
    loadingIndicatorService.onLoadingChanged.subscribe(isLoading => {
       this.loading = isLoading
       this.ref.detectChanges();
    });
 }

答案 1 :(得分:0)

在一个更改检测生命周期中,如果表达式更改了多次,则抛出ExpressionChangedAfterItHasBeenCheckedError(在开发人员模式下)。那么在您的示例中它如何变化?

生命周期1:

  • loading: boolean = false;AppComponent的构造函数中
  • hello组件在onInit中发出http请求。然后触发拦截器,更新可观察对象。

生命周期2(在开发人员模式下,检查所有值是否稳定/仍然相同):

  • loading现在为true,因为对可观察对象的预订更改了该值,该值不同于先前的loading = false的值,因此Angular引发了错误。

要缓解此问题,您可以:

  • 将代码从onInit移动到构造函数。缺点:不建议在构造函数中做任何事情
  • 如Sajeetharan所述,
  • 包括cdr。缺点:您必须在可观察对象的每个客户端上执行此操作。
  • 在超时setTimeout(() => myObservable.next(...));后发出更改后的可观察值。缺点:很丑。但是,如果您希望多个客户端订阅您的可观察对象,则可能会给您带来最少的麻烦,因为您不必在所有地方都包含cdr。

老实说,这些解决方案都不是特别漂亮。

请注意,async管道与另一个答案中建议的问题无关。

答案 2 :(得分:-1)

AsyncPipe救援:

<div *ngIf="loadingIndicatorService.onLoadingChanged | async">Loading...</div>

请记住将服务更改为公开。

编辑:还需要将changeDetectionStrategy设置为OnPush。工作闪电战: https://stackblitz.com/edit/angular-ay1ikq

至于为什么即使使用AsyncPipe而没有OnPush也会发生这种情况,不能说。也许有人对Angular核心有深入的了解?在拦截器之前/之后内部触发CD?我几乎总是使用OnPush,但必须有一个我最初没有的情况,并且仍然没有看到AsyncPipe发生这种错误。唯一与我所做的事情完全不同的是使用拦截器来做一些导致模板绑定值的变化,这样才能成为根本原因?