首页 文章

ExpressionChangedAfterItHasBeenCheckedError解释

提问于
浏览
115

请向我解释为什么我一直收到此错误: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

显然,我只是在开发模式下获得它,它不会在我的 生产环境 版本中发生,但它非常烦人,而我根本不理解在我的开发环境中出现错误的好处,这些错误不会出现在prod上 - - 可能是因为我缺乏理解 .

通常,修复很容易,我只是将错误导致代码包装在setTimeout中,如下所示:

setTimeout(()=> {
    this.isLoading = true;
}, 0);

或者使用如下构造函数强制检测更改: constructor(private cd: ChangeDetectorRef) {}

this.isLoading = true;
this.cd.detectChanges();

但为什么我经常遇到这个错误?我想了解它,以便将来可以避免这些hacky修复 .

16 回答

  • 53

    我有一个类似的问题 . 看着lifecycle hooks documentation,我将 ngAfterViewInit 更改为 ngAfterContentInit 并且有效 .

  • 3

    此错误表示应用程序中存在实际问题,因此抛出异常是有意义的 .

    devMode 中,更改检测会在每次常规更改检测运行后添加一个额外的转弯,以检查模型是否已更改 .

    如果模型在常规和附加变化检测转弯之间发生了变化,则表明这两者之一

    • 变化检测本身已经引起了变化

    • 方法或getter每次调用时都返回一个不同的值

    哪些都不好,因为不清楚如何继续,因为模型可能永远不会稳定 .

    如果Angular运行更改检测直到模型稳定,它可能会永远运行 . 如果Angular未运行更改检测,则视图可能不会反映模型的当前状态 .

    另见What is difference between production and development mode in Angular2?

  • 0

    Update

    我强烈建议首先从the OP's self response开始:正确考虑 constructor 中可以做什么与 ngOnChanges() 应该做什么 .

    Original

    这是一个侧面说明而不是答案,但它可能对某人有所帮助 . 当我试图使按钮的存在取决于表单的状态时,我偶然发现了这个问题:

    <button *ngIf="form.pristine">Yo</button>
    

    据我所知,这种语法导致按钮根据条件添加和删除DOM . 这又导致 ExpressionChangedAfterItHasBeenCheckedError .

    在我的情况下修复(虽然我没有声称掌握差异的全部含义),是使用 display: none 代替:

    <button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>
    
  • 0

    一旦我理解了the Angular Lifecycle Hooks以及它们与变化检测的关系,就会有很多理解 .

    我试图让Angular更新绑定到元素的 *ngIf 的全局标志,并且我试图在另一个组件的 ngOnInit() 生命周期钩子内更改该标志 .

    根据文档,在Angular已经检测到更改后调用此方法:

    在第一次ngOnChanges()之后调用一次 .

    所以更新 ngOnChanges() 中的标志赢了't initiate change detection. Then, once change detection has naturally triggered again, the flag'的值已经改变并且抛出了错误 .

    在我的情况下,我改变了这个:

    constructor(private globalEventsService: GlobalEventsService) {
    
    }
    
    ngOnInit() {
        this.globalEventsService.showCheckoutHeader = true;
    }
    

    对此:

    constructor(private globalEventsService: GlobalEventsService) {
        this.globalEventsService.showCheckoutHeader = true;
    }
    
    ngOnInit() {
    
    }
    

    它解决了问题:)

  • 0

    在我的情况下,我在我的spec文件中遇到了这个问题,同时运行我的测试 .

    我不得不改变 ngIf to [hidden]

    <app-loading *ngIf="isLoading"></app-loading>
    

    to

    <app-loading [hidden]="!isLoading"></app-loading>
    
  • 13

    请按照以下步骤操作:

    1.使用'ChangeDetectorRef'从@ angular / core导入它,如下所示:

    import{ ChangeDetectorRef } from '@angular/core';
    

    2.在构造函数()中实现它,如下所示:

    constructor(   private cdRef : ChangeDetectorRef  ) {}
    

    3.将以下方法添加到您正在调用单击按钮等事件的函数中 . 所以它看起来像这样:

    functionName() {   
        yourCode;  
        //add this line to get rid of the error  
        this.cdRef.detectChanges();     
    }
    
  • 9

    我遇到了同样的问题,因为我的组件中的一个数组中的值发生了变化 . 但是,我没有检测到值变化的变化,而是将组件更改检测策略更改为 onPush (它将检测对象更改的更改而不是值更改) .

    import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
    
    @Component({
        changeDetection: ChangeDetectionStrategy.OnPush
        selector: -
        ......
    })
    
  • 8

    参考文章https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

    因此,变更检测背后的机制实际上是以同步执行变更检测和验证摘要的方式工作的 . 这意味着,如果我们异步更新属性,则在验证循环运行时不会更新值,并且我们不会收到 ExpressionChanged... 错误 . 我们得到此错误的原因是,在验证过程中,Angular看到的值与更改检测阶段记录的值不同 . 所以要避免......

    1)使用changeDetectorRef

    2)使用setTimeOut . 这将作为宏任务在另一个VM中执行您的代码 . Angular在验证过程中不会看到这些更改,您将不会收到该错误 .

    setTimeout(() => {
            this.isLoading = true;
        });
    

    3)如果你真的想在同一个VM上执行代码,请使用

    Promise.resolve(null).then(() => this.isLoading = true);
    

    这将创建一个微任务 . 在当前同步代码完成执行之后处理微任务队列,因此在验证步骤之后将对属性进行更新 .

  • 0

    有一些有趣的答案,但我似乎找不到符合我需要的答案,最接近的是来自@ chittrang-mishra,它只涉及一个特定的功能而不是我的应用程序中的几个切换 .

    我不想使用 [hidden] 来利用 *ngIf 甚至不是DOM的一部分所以我发现以下解决方案可能不是最好的,因为它抑制错误而不是纠正它,但在我的情况下,我知道最终结果是正确的,我的应用程序似乎没问题 .

    我所做的是实现 AfterViewChecked ,然后添加 constructor(private changeDetector : ChangeDetectorRef ) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }
    

    我希望这有助于其他许多人帮助我 .

  • 8

    我在Ionic3中遇到了这种错误(它使用Angular 4作为其技术堆栈的一部分) .

    对我来说它是这样做的:

    <ion-icon [name]="getFavIconName()"></ion-icon>

    所以我试图根据屏幕运行的模式有条件地将ion-icon的类型从 pin 更改为 remove-circle .

    我猜我不得不添加一个* ngIF .

  • 58

    这是我对正在发生的事情的看法 . 我没有阅读文档,但我确定这是错误显示的部分原因 .

    *ngIf="isProcessing()"
    

    使用* ngIf时,它会在每次条件更改时通过添加或删除元素来物理更改DOM . 因此,如果条件在渲染到视图之前发生更改(这在Angular的世界中非常可能),则会引发错误 . 请参阅开发和 生产环境 模式之间的解释here .

    [hidden]="isProcessing()"
    

    使用[hidden]时,它不会物理地更改DOM,只是将元素隐藏在视图中,很可能在后面使用CSS . 元素仍在DOM中,但根据条件的值不可见 . 这就是使用[hidden]时不会发生错误的原因 .

  • 1

    对于我的问题,我正在阅读github - "ExpressionChangedAfterItHasBeenCheckedError when changing a component 'non model' value in afterViewInit"并决定添加ngModel

    <input type="hidden" ngModel #clientName />
    

    它修复了我的问题,我希望它可以帮助某人 .

  • 24

    @HostBinding可能是此错误的混乱来源 .

    例如,假设您在组件中具有以下主机绑定

    // image-carousel.component.ts
    @HostBinding('style.background') 
    style_groupBG: string;
    

    为简单起见,我们可以通过以下输入属性更新此属性:

    @Input('carouselConfig')
    public set carouselConfig(carouselConfig: string) 
    {
        this.style_groupBG = carouselConfig.bgColor;   
    }
    

    在父组件中,您以编程方式在 ngAfterViewInit 中设置它

    @ViewChild(ImageCarousel) carousel: ImageCarousel;
    
    ngAfterViewInit()
    {
        this.carousel.carouselConfig = { bgColor: 'red' };
    }
    

    这是发生的事情:

    • 您的父组件已创建

    • 创建了ImageCarousel组件,并将其分配给 carousel (通过ViewChild)

    • 我们无法访问 carousel ,直到 ngAfterViewInit() (它将为null)

    • 我们分配配置,设置 style_groupBG = 'red'

    • 这反过来在主机ImageCarousel组件上设置 background: red

    • 此组件由您的父组件'owned',因此当它检查更改时,它会在 carousel.style.background 上发现更改并且不会出现问题,因此会抛出异常 .

    一种解决方案是引入另一个包装器div内部ImageCarousel并在其上设置背景颜色,但是你没有获得使用 HostBinding 的一些好处(例如允许父控件控制对象的完整边界) .

    在父组件中更好的解决方案是在设置配置后添加detectChanges() .

    ngAfterViewInit()
    {
        this.carousel.carouselConfig = { ... };
        this.cdr.detectChanges();
    }
    

    这可能看起来非常明显,与其他答案非常相似,但有一个微妙的区别 .

    考虑在开发过程中稍后不添加 @HostBinding 的情况 . 突然间你得到了这个错误,似乎没有任何意义 .

  • 1

    调试提示

    这个错误可能会让人很困惑,并且很容易就错误的假设做出错误的假设 . 我发现在相应位置的受影响组件中添加许多这样的调试语句很有帮助 . 这有助于理解流程 .

    在像这样的父put语句中(确切的字符串'EXPRESSIONCHANGED'很重要),但除此之外这些只是示例:

    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
        console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
        console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
        console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');
    

    在子/ services / timer回调中:

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
        console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');
    

    如果你手动运行 detectChanges 也为此添加日志记录:

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
        this.cdr.detectChanges();
    

    然后在Chrome调试器中只过滤'EXPRESSIONCHANGES' . 这将准确显示所有设置的流程和顺序,以及Angular引发错误的确切位置 .

    enter image description here

    您还可以单击灰色链接以放置断点 .

    另外要注意的是,如果在整个应用程序中有类似的命名属性(例如 style.background ),请确保通过将其设置为模糊的颜色值来调试您认为的那个 .

  • 25

    当我添加 *ngIf 时,我的问题就显而易见了,但那不是原因 . 该错误是由更改 {{}} 标记中的模型然后尝试在 *ngIf 语句中显示更改的模型引起的 . 这是一个例子:

    <div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
    ....
    <div *ngIf="true">{{myModel.value}}</div>
    

    为了解决这个问题,我将changeMyModelValue()调用到了一个更有意义的地方 .

    在我的情况下,我希望每当子组件更改数据时调用changeMyModelValue() . 这需要我在子组件中创建并发出一个事件,以便父组件可以处理它(通过调用changeMyModelValue() . 请参阅https://angular.io/guide/component-interaction#parent-listens-for-child-event

  • 0

    Angular运行更改检测,当它发现已传递给子组件的某些值已更改时,Angular会抛出错误ExpressionChangedAfterItHasBeenCheckedError click for More

    为了纠正这个问题,我们可以使用AfterContentChecked生命周期钩和

    import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';
    
      constructor(
      private cdref: ChangeDetectorRef) { }
    
      ngAfterContentChecked() {
    
        this.cdref.detectChanges();
    
      }
    

相关问题