Пожалуйста, объясните мне, почему я продолжаю получать эту ошибку: ExpressionChangedAfterItHasBeenCheckedError: Выражение изменилось после проверки.

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

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

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

Или принудительно обнаружить изменения с помощью такого конструктора: constructor (частный cd: ChangeDetectorRef) {}:

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

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

Ответы (29)

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

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

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

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

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

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

constructor(private globalEventsService: GlobalEventsService) {

}

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

На это:

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

ngOnInit() {

}

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

Обновить

Я настоятельно рекомендую начать с собственного ответа OP сначала: правильно подумайте о том, что можно сделать в конструкторе по сравнению с тем, что должно быть сделано в ngOnChanges ().

Исходный

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


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

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


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

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

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

Полагаю, мне придется добавить * ngIf.

Ссылаясь на статью 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);

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

@ 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 создается и назначается карусели (через ViewChild)
  • Мы не можем получить доступ к карусели до ngAfterViewInit () (будет null)
  • Назначаем конфигурацию, которая устанавливает style_groupBG = 'red'
  • Это, в свою очередь, устанавливает фон: красный в компоненте ImageCarousel хоста
  • Этот компонент «принадлежит» вашему родительскому компоненту, поэтому, когда он проверяет наличие изменений, он находит изменение в carousel.style.background и недостаточно умен, чтобы знать, что это не проблема, поэтому возникает исключение.

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

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

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

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

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

Надеюсь, это поможет кому-нибудь сюда прийти: Мы выполняем сервисные вызовы в ngOnInit следующим образом и используем переменную displayMain для управления монтированием элементов в DOM.

component.ts

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

и component.html

Моя проблема была очевидна, когда я добавил * ngIf, но это не было причиной. Ошибка была вызвана изменением модели в тегах {{}}, а затем попыткой отобразить измененную модель в операторе * ngIf позже. Вот пример:

{{changeMyModelValue()}}
....
{{myModel.value}}

Чтобы решить эту проблему, я изменил место вызова changeMyModelValue () на более понятное.

В моей ситуации я хотел, чтобы changeMyModelValue () вызывалась всякий раз, когда дочерний компонент изменял данные. Для этого нужно было создать и испустить событие в дочернем компоненте, чтобы родитель мог его обработать (путем вызова changeMyModelValue (). См. https://angular.io/guide/component-interaction#parent-listens-for-child-event

Были интересные ответы, но я, похоже, не нашел ни одного, соответствующего моим потребностям, наиболее близким из которых был @ chittrang-mishra, который относится только к одной конкретной функции, а не к нескольким переключателям, как в моем приложении.

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

Я реализовал AfterViewChecked, добавил конструктор (частный changeDetector: ChangeDetectorRef) {}, а затем

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

Надеюсь, это поможет другим, как и многие другие помогли мне.

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

ExpressionChangedAfterItHasBeenCheckedError щелкните, чтобы увидеть больше

Чтобы исправить это, мы можем использовать AfterContentChecked жизненный цикл и

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }

Я получил эту ошибку, потому что я использовал переменную в component.html, которая не была объявлена ​​в component.ts. Как только я удалил часть в HTML, эта ошибка исчезла.

I'm using ng2-carouselamos (Angular 8 and Bootstrap 4)

Taking these steps fixed my problem:

  1. Implement AfterViewChecked
  2. Add constructor(private changeDetector : ChangeDetectorRef ) {}
  3. Then ngAfterViewChecked(){ this.changeDetector.detectChanges(); }

Пробовал большинство решений, предложенных выше. Только это сработало для меня в этом сценарии. Я использовал * 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 обязательно вызовет проблемы с производительностью, поскольку ваше приложение становится больше, поскольку оно запускается много раз.

У меня была эта проблема между RxJS / Observables и статическими фиктивными данными. Сначала в моем приложении использовались статические фиктивные данные, в моем случае массивы данных

HTML был таким:

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

Таким образом, идея заключалась в отображении до 10 элементов из myArray.splice () берет копию исходного массива. Насколько мне известно, это прекрасно в Angular.

Затем Я изменил поток данных на наблюдаемый шаблон, поскольку мои «настоящие» данные поступают из Akita (библиотека управления состоянием). Это означает, что мой html стал:

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

, где myArray $ - это [был] тип Observable , и именно эта манипуляция с данными в шаблоне вызвала ошибку. Не делайте этого с объектами RxJS.

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

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:

{{ frequentlyChangingValue }}

Основной подход здесь состоит в том, что каждый цикл обнаружения изменений имеет свой собственный uuid. Когда uuid изменяется, вы знаете, что находитесь в следующем цикле. Если цикл изменился, обновите значение и верните его, иначе просто верните то же значение, которое было ранее возвращено в этом цикле.

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

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

Моя проблема заключалась в том, что я открывал всплывающее окно Ngbmodal при загрузке этого объекта, который изменялся после проверки. Мне удалось решить эту проблему, открыв модальное всплывающее окно внутри setTimeout.

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

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

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

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

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

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

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

Решение, которое сработало для меня с использованием rxjs

импорт {startWith, tap, delay} из 'rxjs / operator';

// Поле данных, используемое для заполнения в HTML
dataSource: любой;

....

ngAfterViewInit () {
  this.yourAsyncData.
      .трубка(
          startWith (ноль),
          задержка (0),
          нажмите ((res) => this.dataSource = res)
      ).подписываться();
}

В моем случае у меня было свойство async в LoadingService с BehavioralSubject isLoading

Использование модели [скрыто] работает, но * ngIf не работает

    

THIS WORKS FINE (Loading Data)

THIS THROWS ERROR (Loading Data)

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

Мне пришлось изменить ngIf на [скрыто]

 

на

 

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

*ngIf="isProcessing()" 

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

[hidden]="isProcessing()"

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

Как 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/

Всем, кто борется с этим. Вот способ правильно отладить эту ошибку: https://blog.angular-university.io/angular-debugging/

В моем случае я действительно избавился от этой ошибки с помощью этого [скрытого] хака вместо * ngIf ...

Но ссылка, которую я предоставил, позволила мне найти ВИНОВИН * ngIf:)

Наслаждайтесь.

Выполните следующие шаги:

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

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

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

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

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

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

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

См. Также В чем разница между производственным режимом и режимом разработки в Angular2?

Что касается моей проблемы, я читал github - «ExpressionChangedAfterItHasBeenCheckedError при изменении значения компонента« не модель »в afterViewInit» и решил добавить ngModel


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

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

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

В родительских операторах 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 выдает ошибку.

enter image description here

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

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

2022 WebDevInsider