简体   繁体   English

Angular - 同一组件中的 ExpressionChangedAfterItHasBeenCheckedError

[英]Angular - ExpressionChangedAfterItHasBeenCheckedError in same Component

I have a simple Angular 5 application with a HttpInterceptor that notifies a service when a request is made.我有一个简单的Angular 5应用程序,它带有一个HttpInterceptor ,它在发出请求时通知服务。 Components can subscribe to this service to get this information.组件可以订阅此服务以获取此信息。

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

In my AppComponent I want to show a loading indicator when request are made in my child component ( HelloComponent ).在我的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);
  }
}

I get the following error:我收到以下错误:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.错误:ExpressionChangedAfterItHasBeenCheckedError:检查后表达式已更改。 Previous value: 'false'.以前的值:'假'。 Current value: 'true'.当前值:“真”。

If I'm correct this error appears when a child component changes a value in a parent component.如果我是正确的,当子组件更改父组件中的值时会出现此错误。

But my child component ( HelloComponent ) is only doing a HttpGet and the HttpInterceptor is changing the parent component ( AppComponent ) over a service with an observable.但是我的子组件( HelloComponent )只执行 HttpGet 并且HttpInterceptor正在通过具有可观察的服务更改父组件( AppComponent )。

Shouldn't changing over a service be the correct solution here?在这里更改服务不应该是正确的解决方案吗? Can someone explain why this doesn't work?有人可以解释为什么这不起作用吗?

It throws an error because your variable loading gets updated when onLoadingChanged is called.它会引发错误,因为在调用onLoadingChanged时您的变量加载会更新。 This means your initial value got changed.这意味着您的初始值已更改。

I would suggest you to read more about here我建议你阅读更多关于here

The general fix for this would be,对此的一般解决方法是,

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

AsyncPipe to the rescue: AsyncPipe 来救援:

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

Remember to change the service into public.记得把服务改成public。

Edit: Needed to also set changeDetectionStrategy to OnPush.编辑:还需要将 changeDetectionStrategy 设置为 OnPush。 Working blitz: https://stackblitz.com/edit/angular-ay1ikq工作闪电战: https : //stackblitz.com/edit/angular-ay1ikq

As for why this is happening without OnPush even with AsyncPipe, can't say.至于为什么即使使用 AsyncPipe 在没有 OnPush 的情况下也会发生这种情况,也不能说。 Maybe someone with deep knowledge into Angular core?也许有人对 Angular 核心有深入了解? CD triggered internally right before/after interceptors? CD 在拦截器之前/之后内部触发? I almost always use OnPush but there has to be a situation where I didn't at first, and still haven't seen this error happen with AsyncPipe.我几乎总是使用 OnPush 但必须有一种情况,我一开始没有使用,并且仍然没有看到 AsyncPipe 发生此错误。 Only thing really different from anything I've done is using interceptors to do something that causes a change in a value bound to a template, so that's got to be the root cause?唯一与我所做的任何事情真正不同的是使用拦截器来做一些导致绑定到模板的值发生变化的事情,所以这一定是根本原因?

The ExpressionChangedAfterItHasBeenCheckedError gets thrown (in developer mode) when an expression changes more than one time during one change detection lifecycle.当表达式在一个更改检测生命周期中更改一次以上时,将引发ExpressionChangedAfterItHasBeenCheckedError (在开发人员模式下)。 So how is it changing in your example?那么在你的例子中它是如何变化的?

Lifecycle #1:生命周期 #1:

  • loading: boolean = false; during the constructor of AppComponentAppComponent的构造函数中
  • The hello component makes the http request in onInit . hello 组件在onInit发出 http 请求。 This then triggers the the interceptor which updates the observable.然后触发更新可观察对象的拦截器。

Lifecycle #2 (in dev mode, to check whether all values are stable / still the same): Lifecycle #2(在开发模式下,检查所有值是否稳定/仍然相同):

  • loading is now true because the subscription to the observable changed the value, which is different than the previous value of loading = false , so Angular throws the error. loading现在为true因为对 observable 的订阅改变了值,这与之前的loading = false值不同,所以 Angular 抛出错误。

To mitigate the problem, you can:为了缓解这个问题,您可以:

  • move the code from onInit to the constructor.将代码从onInit移动到构造函数。 downside: it's not recommended to do stuff in the constructor缺点:不建议在构造函数中做任何事情
  • include cdr as Sajeetharan describes.包括 Sajeetharan 描述的 cdr。 downside: you'll have to do this on every client of your observable.缺点:您必须在可观察对象的每个客户端上执行此操作。
  • emit the changed observable value after a timeout setTimeout(() => myObservable.next(...));在超时后发出更改的 observable 值setTimeout(() => myObservable.next(...)); . . downside: it's ugly.缺点:丑。 However it might cause you the least trouble if you expect multiple clients to subscribe to your observable, as you don't have to include cdr everywhere.但是,如果您希望多个客户端订阅您的 observable,这可能会给您带来最少的麻烦,因为您不必在任何地方都包含 cdr。

To be honest, none of those solutions are particularly pretty.老实说,这些解决方案都不是特别漂亮。

Note that the async pipe has nothing to do with the problem as suggested in another answer.请注意, async管道与另一个答案中建议的问题无关。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 动态创建的组件中的Angular ExpressionChangedAfterItHasBeenCheckedError - Angular ExpressionChangedAfterItHasBeenCheckedError in dynamically created component Angular动态组件加载 - ExpressionChangedAfterItHasBeenCheckedError - Angular dynamic component loading - ExpressionChangedAfterItHasBeenCheckedError 角度:ExpressionChangedAfterItHasBeenCheckedError子组件和FormControl - Angular: ExpressionChangedAfterItHasBeenCheckedError child component & FormControl 角度组件ExpressionChangedAfterItHasBeenCheckedError中的Mat对话框错误 - Mat Dialog Error in Angular Component ExpressionChangedAfterItHasBeenCheckedError 在Angular 4组件中的哪里初始化变量-ExpressionChangedAfterItHaHasBeenCheckedError - Where to initialize variables in an Angular 4 component - ExpressionChangedAfterItHasBeenCheckedError ExpressionChangedAfterItHasBeenCheckedError:角度 - ExpressionChangedAfterItHasBeenCheckedError: angular ExpressionChangedAfterItHasBeenCheckedError Angular - ExpressionChangedAfterItHasBeenCheckedError Angular Angular 4 ExpressionChangedAfterItHasBeenCheckedError - Angular 4 ExpressionChangedAfterItHasBeenCheckedError 每个步骤都有单独组件的角度材质步进器 - ExpressionChangedAfterItHasBeenCheckedError - Angular Material Stepper with Separate Component for each step - ExpressionChangedAfterItHasBeenCheckedError 使用同步Observables测试Angular组件时出现错误:ExpressionChangedAfterItHasBeenCheckedError - Error: ExpressionChangedAfterItHasBeenCheckedError when an Angular Component is tested with synchronous Observables
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM