簡體   English   中英

ExpressionChangedAfterItHasBeenCheckedError 解釋

[英]ExpressionChangedAfterItHasBeenCheckedError Explained

請向我解釋為什么我不斷收到此錯誤: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

顯然,我只在開發模式下得到它,它不會在我的生產構建中發生,但它非常煩人,我根本不明白在我的開發環境中出現不會出現在產品上的錯誤的好處 - -可能是因為我缺乏理解。

通常,修復很簡單,我只是將導致錯誤的代碼包裝在 setTimeout 中,如下所示:

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

或者使用這樣的構造函數強制檢測更改: constructor(private cd: ChangeDetectorRef) {}

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

但是為什么我經常遇到這個錯誤? 我想了解它,以便將來避免這些 hacky 修復。

我有一個類似的問題。 查看生命周期掛鈎文檔,我將ngAfterViewInit更改為ngAfterContentInit並且它有效。

此錯誤表明您的應用程序中存在真正的問題,因此引發異常是有意義的。

devMode更改檢測會在每次常規更改檢測運行后添加一個額外的輪次,以檢查模型是否已更改。

如果模型在常規和附加變化檢測輪次之間發生了變化,這表明要么

  • 變化檢測本身已經引起了變化
  • 每次調用方法或 getter 時都會返回不同的值

這兩者都很糟糕,因為模型可能永遠不會穩定,因此不清楚如何進行。

如果 Angular 在模型穩定之前運行變化檢測,它可能會一直運行。 如果 Angular 不運行變更檢測,那么視圖可能不會反映模型的當前狀態。

另請參閱Angular2中的生產模式和開發模式有什么區別?

一旦我了解了 Angular Lifecycle Hooks及其與變更檢測的關系,就會產生很多理解。

我試圖讓 Angular 更新綁定到元素的*ngIf的全局標志,並且我試圖在另一個組件的ngOnInit()生命周期掛鈎內更改該標志。

根據文檔,在 Angular 已經檢測到更改后調用此方法:

在第一個 ngOnChanges() 之后調用一次。

因此更新ngOnChanges()內部的標志不會啟動更改檢測。 然后,一旦更改檢測再次自然觸發,標志的值就會發生更改並引發錯誤。

就我而言,我改變了這個:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

對此:

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

ngOnInit() {

}

它解決了問題:)

Angular 運行更改檢測,當它發現某些已傳遞給子組件的值已更改時,Angular 會拋出以下錯誤:

ExpressionChangedAfterItHasBeenCheckedError 點擊了解更多

為了糾正這個問題,我們可以使用AfterContentChecked生命周期鈎子和

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }

我正在使用ng2-carouselamos (Angular 8 和 Bootstrap 4)

采取這些步驟解決了我的問題:

  1. 實施AfterViewChecked
  2. 添加constructor(private changeDetector : ChangeDetectorRef ) {}
  3. 然后ngAfterViewChecked(){ this.changeDetector.detectChanges(); } ngAfterViewChecked(){ this.changeDetector.detectChanges(); }

更新

我強烈建議首先從OP 的自我響應開始:正確考慮在constructor中可以做什么與在ngOnChanges()中應該做什么。

原來的

這與其說是一個答案,不如說是一個旁注,但它可能會對某人有所幫助。 在嘗試使按鈕的存在取決於表單的狀態時,我偶然發現了這個問題:

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

據我所知,這種語法會導致按鈕根據條件從 DOM 中添加和刪除。 這反過來導致ExpressionChangedAfterItHasBeenCheckedError

在我的情況下的修復(盡管我沒有聲稱掌握差異的全部含義)是使用display: none代替:

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

有一些有趣的答案,但我似乎沒有找到一個符合我需求的答案,最接近的是來自 @chittrang-mishra 的答案,它僅指一個特定的功能,而不是我的應用程序中的幾個切換。

我不想使用[hidden]來利用*ngIf甚至不是 DOM 的一部分,所以我發現以下解決方案可能不是對所有人最好的,因為它抑制錯誤而不是糾正錯誤,但在我的如果我知道最終結果是正確的,我的應用程序似乎還可以。

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

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

我希望這對其他人有所幫助,因為許多其他人已經幫助了我。

請按照以下步驟操作:

1. 通過從@angular/core 導入來使用'ChangeDetectorRef',如下所示:

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

2.在constructor()中實現如下:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3. 將以下方法添加到您在單擊按鈕等事件上調用的函數中。 所以它看起來像這樣:

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

就我而言,我在運行測試時在我的規范文件中遇到了這個問題。

我不得不將ngIf更改[hidden]

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

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

我面臨着同樣的問題,因為我的組件中的一個數組中的值正在發生變化。 但是我沒有檢測值變化的變化,而是將組件變化檢測策略更改為onPush (它將檢測對象變化而不是值變化)。

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})

參考文章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);

這將創建一個微任務。 微任務隊列在當前同步代碼執行完畢后處理,因此屬性的更新將在驗證步驟之后發生。

嘗試了上面建議的大多數解決方案。 在這種情況下,只有這對我有用。 我正在使用 *ngIf 根據 api 調用來切換角度材料的不確定進度條,它正在拋出ExpressionChangedAfterItHasBeenCheckedError

在有問題的組件中:

constructor(
    private ngZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
) {}

ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
        this.appService.appLoader$.subscribe(value => {
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        });
    });
}

訣竅是使用 ngzone 繞過角度組件的變化檢測。

PS:不確定這是否是一個優雅的解決方案,但使用 AfterContentChecked 和 AfterViewChecked 生命周期鈎子必然會引發性能問題,因為您的應用程序會因為它被多次觸發而變得更大。

@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)
  • ngAfterViewInit()之前我們無法訪問carousel (它將為空)
  • 我們分配配置,設置style_groupBG = 'red'
  • 這反過來在主機 ImageCarousel 組件上設置background: red
  • 該組件由您的父組件“擁有”,因此當它檢查更改時,它會在carousel.style.background上發現更改,並且不夠聰明,無法知道這不是問題,因此會引發異常。

一種解決方案是引入另一個包裝器 div 內部 ImageCarousel 並在其上設置背景顏色,但是您不會獲得使用HostBinding的一些好處(例如允許父級控制對象的完整邊界)。

更好的解決方案是在父組件中設置配置后添加detectChanges()。

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

這可能看起來很明顯,並且與其他答案非常相似,但存在細微差別。

考慮在開發后期才添加@HostBinding的情況。 突然你得到這個錯誤,它似乎沒有任何意義。

這是我對正在發生的事情的想法。 我還沒有閱讀文檔,但我確信這是顯示錯誤的部分原因。

*ngIf="isProcessing()" 

使用 *ngIf 時,它會在每次條件更改時通過添加或刪除元素來物理更改 DOM。 因此,如果條件在呈現到視圖之前發生變化(這在 Angular 的世界中很有可能),就會引發錯誤。 請參閱此處在開發和生產模式之間的解釋。

[hidden]="isProcessing()"

當使用[hidden]時,它不會物理地改變DOM ,而只是從視圖中隱藏element ,很可能在后面使用CSS 該元素仍然存在於 DOM 中,但根據條件的值不可見。 這就是為什么使用[hidden]時不會發生錯誤的原因。

調試提示

這個錯誤可能會非常令人困惑,並且很容易對它發生的確切時間做出錯誤的假設。 我發現在受影響的組件中的適當位置添加大量這樣的調試語句很有幫助。 這有助於理解流程。

在這樣的父 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');

在子/服務/計時器回調中:

    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 在什么時候拋出錯誤。

在此處輸入圖像描述

您也可以單擊灰色鏈接來放置斷點。

如果您在整個應用程序中具有類似命名的屬性(例如style.background ),請注意另一件事,請確保您正在調試您認為的屬性 - 通過將其設置為模糊的顏色值。

如果您在ngAfterViewInit()中觸發EventEmitter時看到ExpressionChangedAfterItHasBeenCheckedError ,那么您可以在創建EventEmitter時傳遞true以使其在當前更改檢測周期后異步發射。

@Component({
  ...
})
export class MyComponent implements AfterViewInit {
  // Emit asynchronously to avoid ExpressionChangedAfterItHasBeenCheckedError
  @Output() valueChange = new EventEmitter<number>(true);

  ...

  ngAfterViewInit(): void {
    ...
    this.valueChange.emit(newValue);
    ...
  }

}

當我添加*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

我在 Ionic3 中遇到了這種錯誤(它使用 Angular 4 作為其技術堆棧的一部分)。

對我來說,它是這樣做的:

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

因此,我試圖根據屏幕運行的模式有條件地將ion-icon的類型從pin更改為remove-circle

我猜我必須添加一個*ngIf來代替。

使用 rxjs 對我有用的解決方案

import { startWith, tap, delay } from 'rxjs/operators';

// Data field used to populate on the html
dataSource: any;

....

ngAfterViewInit() {
  this.yourAsyncData.
      .pipe(
          startWith(null),
          delay(0),
          tap((res) => this.dataSource = res)
      ).subscribe();
}

對於我的問題,我正在閱讀github - “ExpressionChangedAfterItHasBeenCheckedError when changed a component 'non model' value in afterViewInit”並決定添加 ngModel

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

它解決了我的問題,我希望它可以幫助某人。

就我而言,我在LoadingService中有一個帶有 BehavioralSubject isLoading的異步屬性

使用[hidden]模型有效,但*ngIf失敗

    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>

對於任何為此而苦苦掙扎的人。 這是正確調試此錯誤的方法: https : //blog.angular-university.io/angular-debugging/

就我而言,確實我使用這個 [hidden] hack 而不是 *ngIf 擺脫了這個錯誤......

但我提供的鏈接,使我能夠找到有罪* ngIf :)

享受。

我在 RxJS/Observables 和靜態模擬數據之間遇到了這個問題。 起初,我的應用程序使用靜態模擬數據,在我的例子中是數據數組

html是這樣的:

*ngFor="let x of myArray?.splice(0, 10)"

所以這個想法是只顯示來自myArray的 10 個元素。 splice()獲取原始數組的副本。 據我所知,這在 Angular 中非常好

然后我將數據流更改為 Observable 模式,因為我的“真實”數據來自Akita (一個狀態管理庫)。 這意味着我的 html 變成了:

*ngFor="let x of (myArray$ | async)?.splice(0, 10)"

其中 myArray$ 是Observable<MyData[]>的 [was] 類型,模板中的這種數據操作是導致錯誤發生的原因。 不要對 RxJS 對象這樣做。

setTimeout 或 delay(0) 如何解決這個問題?

這就是上面的代碼解決問題的原因:

The initial value of the flag is false, and so the loading indicator will NOT be displayed initially

ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit()

Angular then finishes rendering the view and reflects the latest data changes on the screen, and the Javascript VM turn completes

One moment later, the setTimeout() call (also used inside delay(0)) is triggered, and only then the data source loads its data

the loading flag is set to true, and the loading indicator will now be displayed

Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed

這次沒有發生錯誤,因此修復了錯誤消息。

來源: https ://blog.angular-university.io/angular-debugging/

我希望這對來到這里的人有所幫助:我們以以下方式在ngOnInit中進行服務調用,並使用變量displayMain來控制元素到 DOM 的掛載。

組件.ts

  displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }

和component.html

<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>

我收到此錯誤是因為我在 component.html 中使用了一個未在 component.ts 中聲明的變量。 一旦我刪除了 HTML 中的部分,這個錯誤就消失了。

我在訂閱范圍中有一個代碼塊,我只是將其更改為較高范圍,所以工作正常。

我收到此錯誤是因為我在模態中調度 redux 操作,而當時沒有打開模態。 在模態組件收到輸入的那一刻,我正在調度動作。 所以我把 setTimeout 放在那里,以確保打開模式,然后分配動作。

不管我得到多少贊成票,讓我告訴你一個小故事。 該警告試圖幫助您正確編程,但是如果您訂閱生命周期內的可觀察變量,請繼續嘗試解決此問題,但不要太着急。 如果您不能解決問題,請按原樣保留代碼,然后繼續進行工作。

我的問題是我在加載這個檢查后正在更改的對象時打開了一個 Ngbmodal 彈出窗口。 我能夠通過打開 setTimeout 內的模式彈出窗口來解決它。

setTimeout(() => {
  this.modalReference = this.modalService.open(this.modal, { size: "lg" });
});

當一個值在同一個變化檢測周期中變化不止一次時,會發生此錯誤。 我遇到了一個 TypeScript getter 的問題,它的返回值變化非常頻繁。 要解決此問題,您可以限制一個值,使其在每個更改檢測周期中只能更改一次,如下所示:

import { v4 as uuid } from 'uuid'

private changeDetectionUuid: string
private prevChangeDetectionUuid: string
private value: Date

get frequentlyChangingValue(): any {
  if (this.changeDetectionUuid !== this.prevChangeDetectionUuid) {
    this.prevChangeDetectionUuid = this.changeDetectionUuid
    this.value = new Date()
  }
  return this.value
}

ngAfterContentChecked() {
  this.changeDetectionUuid = uuid()
}

HTML:

<div>{{ frequentlyChangingValue }}</div>

這里的基本做法是,每個變更檢測周期都有自己的 uuid。 當 uuid 發生變化時,您就知道您處於下一個循環中。 如果循環已更改,則更新該值並返回它,否則只返回與此循環中先前返回的相同的值。

這確保了每個循環只返回一個值。 鑒於更改檢測周期如此頻繁地發生,這對於頻繁更新值非常有效。

為了生成 uuid,我使用了 uuid npm 模塊,但您可以使用任何生成唯一隨機 uuid 的方法。

檢查您的代碼,有時當您在 lambda 過濾器或 find 表達式中使用“=”而不是“===”時會出現它。 我剛剛在我的一個服務模塊中遇到了這個。

let myVar = this.arrayOfObjects.find(item => item.id = objectId);

以下是正確的:

let myVar = this.arrayOfObjects.find(item => item.id === objectId);

static: true添加到@ViewChild裝飾器

@ViewChild(UploadComponent, { static: true }) uploadViewChild: UploadComponent;

ExpressionChangedAfterItHasBeenCheckedError 錯誤是在進行角度開發時可能發生的運行時錯誤。 它表示在檢查組件視圖后檢測到組件模板的更改,這可能導致意外行為。

當正在檢查組件的視圖時,通常會發生此錯誤,並且對組件的模板或數據綁定進行更改會導致重新檢查視圖。 如果你想知道幕后發生了什么......這不是錯誤,這是一個功能錯誤在本文中完全逆向工程

https://blog.simplified.courses/angular-expression-changed-after-it-has-been-checked-error/

解決方案...服務和 rxjs...事件發射器和屬性綁定都使用 rxjs..您最好自己實現它,更多控制,更容易調試。 請記住,事件發射器正在使用 rxjs。 簡單地說,創建一個服務並在一個 observable 中,讓每個組件訂閱 tha 觀察者,並根據需要傳遞新值或 cosume 值

我在這個問題上掙扎了一段時間,主要是在我的開發環境中運行測試時。 我能夠通過圍繞服務調用響應的簡單 setTimeout 解決問題!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM