Подтвердить что ты не робот

ExpressionChangedAfterItHasBeenCheckedError Explained

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

constructor(private globalEventsService: GlobalEventsService) {

}

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

К этому:

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

ngOnInit() {

}

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

Обновить

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

оригинал

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

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

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

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

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

Ответ 5

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

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

То, что я сделал, это реализовать AfterViewChecked, добавить constructor(private changeDetector: ChangeDetectorRef ) {} а затем

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

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

Ответ 6

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

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

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

в

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }

Ответ 10

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

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

1) Использовать changeDetectorRef

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

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

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

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

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

Ответ 11

@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-инсайдера ImageCarousel и установка цвета фона на него, но тогда вы не получите некоторые преимущества использования HostBinding (например, позволяя родительскому HostBinding контролировать полные границы объекта).

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

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

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

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

Ответ 12

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

Для меня это делалось так:

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

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

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

Ответ 13

Для моей проблемы я читал github - "ExpressionChangedAfterItHasBeenCheckedError при изменении значения" нет модели "компонента в afterViewInit" и решил добавить ngModel

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

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

Ответ 14

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

*ngIf="isProcessing()" 

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

[hidden]="isProcessing()"

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

Ответ 15

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

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

В родительских операторах, подобных этому (важна точная строка "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/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), убедитесь, что вы отлаживаете тот, который, по вашему мнению, вы, установив его на неясное значение цвета.

Ответ 16

Моя проблема проявилась, когда я добавил *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

Ответ 17

В моем случае у меня было асинхронное свойство в LoadingService с поведенческим LoadingService isLoading

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

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

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

Ответ 18

Решение, которое работало для меня, используя 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();
}

Ответ 19

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

Ответ 20

Я надеюсь, что это поможет кому-то прийти сюда: мы ngOnInit сервисные вызовы в 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

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

Ответ 21

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

Ответ 22

У меня был блок кода в области подписки, я просто изменил его на верхнюю область, и он работает нормально.

Ответ 23

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