ExpressionChangedAfterItHasBeenCheckederror Пояснил

пожалуйста, объясните мне, почему я продолжаю получать эту ошибку:ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

очевидно, я получаю его только в режиме dev, это не происходит в моей производственной сборке, но это очень раздражает, и я просто не понимаю преимуществ наличия ошибки в моей среде разработки, которая не будет отображаться на prod-вероятно, из-за моего отсутствия понимания.

обычно исправление достаточно просто, я просто обертываю код, вызывающий ошибку, в setTimeout, как это:

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

или силы обнаруживают изменения с конструктором такой: constructor(private cd: ChangeDetectorRef) {}:

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

но почему я постоянно сталкиваюсь с этой ошибкой? Я хочу понять это, чтобы я мог избежать этих хакерских исправлений в будущем.

14 ответов


у меня была аналогичная проблема. Глядя на жизненный цикл крючки документации, Я изменил ngAfterViewInit до ngAfterContentInit и это сработало.


эта ошибка указывает на реальную проблему в вашем приложении, поэтому имеет смысл создать исключение.

на devMode обнаружение изменений добавляет дополнительный поворот после каждого регулярного запуска обнаружения изменений, чтобы проверить, изменилась ли модель.

Если модель изменилась между обычным и дополнительным поворотом обнаружения изменений, это означает, что либо

  • само обнаружение изменений вызвало изменение
  • метод или геттер возвращает другое значение каждый раз, когда он называется

которые оба плохи, потому что неясно, как действовать, потому что модель может никогда не стабилизироваться.

Если Angular запускает обнаружение изменений, пока модель не стабилизируется, она может работать вечно. Если Angular не запускает обнаружение изменений, то представление может не отражать текущее состояние модели.

см. также в чем разница между производством и режиме развития Angular2?


это скорее Примечание, чем ответ, но это может помочь кому-то. Я наткнулся на эту проблему при попытке сделать наличие кнопки зависит от состояния вида:

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

насколько я знаю, этот синтаксис приводит к добавлению и удалению кнопки из DOM на основе условия. Что в свою очередь приводит к ExpressionChangedAfterItHasBeenCheckedError.

исправление в моем случае (хотя я не утверждаю, что понимаю все последствия разницы), было использовать display: none вместо этого:

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

много понимания пришло, как только я понял угловые крючки жизненного цикла и их связь с обнаружением изменений.

Я пытался сделать угловой, чтобы обновить глобальный флаг, привязанный к *ngIf элемента, и я пытался изменить этот флаг внутри ngOnInit() крюк жизненного цикла другого компонента.

согласно документации, этот метод вызывается после того, как Angular уже обнаружил изменения:

вызывается один раз, после первого ngOnChanges().

таким образом, обновление флага внутри ngOnChanges() не инициирует обнаружение изменений. Затем, как только обнаружение изменений, естественно, запускается снова, значение флага изменилось, и возникает ошибка.

в моем случае, я изменил это:

constructor(private globalEventsService: GlobalEventsService) {

}

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

для этого:

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

ngOnInit() {

}

и это исправило проблему:)


в моем случае у меня была эта проблема в моем файле спецификации во время выполнения моих тестов.

мне пришлось изменить ngIf to [hidden]

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

to

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

я столкнулся с той же проблемой, что и изменение значения в одном из массивов моего компонента. Но вместо обнаружения изменений при изменении значения я изменил стратегию обнаружения изменения компонента на onPush (который будет обнаруживать изменения при изменении объекта, а не при изменении значения).

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

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

выполните следующие действия:

1. Используйте 'ChangeDetectorRef', импортируя его из @angular / core следующим образом:

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();     
}

ссылка на статью https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

таким образом, механика обнаружения изменений на самом деле работает таким образом, что как обнаружение изменений, так и дайджесты проверки выполняются синхронно. Это означает, что если мы обновляем свойства асинхронно, значения не будут обновляться при запуске цикла проверки, и мы не получим ExpressionChanged... ошибка. Причина, по которой мы получаем эту ошибку, заключается в том, что в процессе проверки Angular видит разные значения, а затем записывает их на этапе обнаружения изменений. Чтобы избежать этого....

1) Используйте changeDetectorRef

2) Используйте setTimeOut. Это выполнит ваш код в другой виртуальной машине как макрозадачу. Angular не увидит этих изменений во время процесса проверки, и вы не получите эту ошибку.

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

3) Если вы действительно хотите выполнить свой код на той же виртуальной машине как

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

Это создаст микро-задачу. Очередь микро-задач обрабатывается после завершения выполнения текущего синхронного кода, поэтому обновление свойства произойдет после шага проверки.


у меня была такая ошибка в Ionic3 (который использует Angular 4 как часть его технологического стека).

для меня это было так:

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

поэтому я пытался условно изменить тип Ион-значок С pin до remove-circle, в режиме, в котором работал экран.

Я предполагаю, что вместо этого мне придется добавить *ngIF.


для моего вопроса я читал github - "ExpressionChangedAfterItHasBeenCheckederror при изменении значения компонента" non model "в afterViewInit" и решил добавить ngModel

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

это исправило мою проблему, я надеюсь, что это поможет кому-то.


вот мои мысли о том, что происходит. Я не читал документацию, но уверен, что это часть того, почему отображается ошибка.

*ngIf="isProcessing()" 

при использовании *ngIf он физически изменяет DOM, добавляя или удаляя элемент при каждом изменении условия. Поэтому, если условие изменяется до того, как оно будет отображено на вид (что очень возможно в мире Angular), возникает ошибка. См. объяснение здесь между развитием и продукцией режим.

[hidden]="isProcessing()"

при использовании [hidden] он физически не изменяет DOM, а просто скрывает элемент из вида, скорее всего, используя CSS сзади. Элемент по-прежнему находится в DOM, но не отображается в зависимости от значения условия. Именно поэтому ошибка не будет возникать при использовании [hidden].


@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'
  • это, в свою очередь, устанавливает background: red на компоненте ImageCarousel хоста
  • этот компонент "принадлежит" вашему родительскому компоненту, поэтому, когда он проверяет наличие изменений, он находит изменение на carousel.style.background и недостаточно умен, чтобы знать что это не проблема, поэтому она создает исключение.

одним из решений является введение другой оболочки div insider ImageCarousel и установить цвет фона на этом, но тогда вы не получите некоторые из преимуществ использования HostBinding (например, разрешение родительскому элементу управлять полными границами объекта).

лучшим решением в Родительском компоненте является добавление detectChanges () после установки конфигурации.

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

это может выглядеть довольно очевидно изложите это так и очень похоже на другие ответы, но есть тонкая разница.

рассмотрим случай, когда вы не добавить @HostBinding до более поздно во время развития. Вдруг вы получаете эту ошибку, и это, кажется, не имеет никакого смысла.


советы по отладке

эта ошибка может быть довольно запутанной, и легко сделать неправильное предположение о том, когда именно это происходит. Я считаю полезным добавить много отладочных операторов, подобных этому, во все затронутые компоненты в соответствующих местах. Это помогает понять поток.

в родительских операторах 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');

в child / услуги / таймер обратного вызова:

    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". Это покажет вам точно поток и порядок всего, что получает набор, а также точно в какой момент угловой бросает ошибку.

enter image description here

вы также можете нажать на серые ссылки, чтобы поместить точки останова.

еще одна вещь, чтобы посмотреть если у вас есть аналогичные именованные свойства во всем приложении (например,style.background) убедитесь, что вы отлаживаете тот, который вы думаете, - установив его в неясное значение цвета.


моя проблема была проявлена, когда я добавил {{}} теги затем пытаются отобразить измененную модель в *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