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


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

643   14  

14 ответов:

у меня была аналогичная проблема. Глядя на lifecycle hooks documentation, Я изменил ngAfterViewInit до ngAfterContentInit и это сработало.

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

In 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до[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: -
    ......
})

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

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

ссылаясь на статью 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 на компоненте host 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');

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

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

Comments

    Ничего не найдено.