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

Как управлять выражением Angular2 "изменилось после проверки", когда свойство компонента зависит от текущего времени и времени

Мой компонент имеет стили, которые зависят от текущей даты и времени. В моем компоненте у меня есть следующая функция.

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmp рассчитывается на основе текущей даты и времени. Цвет меняется, если dtoDate за последние 24 часа.

Точная ошибка заключается в следующем:

Выражение изменилось после того, как оно было проверено. Предыдущее значение: "hsl (123, 80%, 49%)". Текущее значение: "hsl (123, 80%, 48%)"

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

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

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

... но безуспешно.

4b9b3361

Ответ 1

Заменить изменение изменений после изменения:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}

Ответ 3

Небольшое решение этой проблемы:

ngAfterViewInit() { // or ngOnInit or whatever
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

Ответ 4

Как упомянуто @leocaseiro по вопросу о github.

Я нашел 3 решения для тех, кто ищет простые исправления.

1) Переход от ngAfterViewInit к ngAfterContentInit

2) Переход к ngAfterViewChecked сочетании с ChangeDetectorRef как предлагается на # 14748 (комментарий)

3) Продолжайте с ngOnInit(), но вызывайте ChangeDetectorRef.detectChanges() после ваших изменений.

Ответ 5

В нашем случае мы ИСПРАВЛЕНЫ, добавив changeDetection в компонент и вызвав DeteChanges() в ngAfterContentChecked, код следующий:

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

Ответ 6

Небольшую работу вокруг я использовал много раз

Promise.resolve(null).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

Ответ 7

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

@Component( {
  selector: 'app-my-component',
  template: '<p>{{ myData?.anyfield }}</p>',
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Протестировано с Angular 5.2.9

Ответ 8

Ей вы идете два решения!

1. Измените ChangeDetectionStrategy на OnPush

Для этого решения вы в основном говорите "angular":

Прекратить проверку на изменения; я сделаю это только тогда, когда мне нужно

Быстрое исправление:

Измените ваш компонент так, чтобы он использовал ChangeDetectionStrategy.OnPush

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}

С этим, кажется, вещи больше не работают. Это потому, что отныне вам придется делать Angular вызов detectChanges() вручную.

this.cdr.detectChanges();

Вот ссылка, которая помогла мне понять ChangeDetectionStrategy правильно: https://alligator.io/angular/change-detection-strategy/

2. Понимание ExpressionChangedAfterItHasBeenCheckedError

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

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

Основной причиной является angular жизненный цикл:

После каждой операции Angular запоминает, какие значения он использовал для выполнения Операция. Они хранятся в свойстве oldValues компонентный вид.

После проверки всех компонентов Angular запускается следующий цикл дайджеста, но вместо выполнения операций он сравнивает текущие значения с теми, из которых он запоминает предыдущий цикл дайджеста.

Следующие операции проверяются в цикле дайджеста:

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

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

проверяет все дочерние компоненты

Итак, выдается ошибка, когда сравниваемые значения отличаются., блогер Макс Корецкий заявил:

Виновником всегда является дочерний компонент или директива.

И, наконец, вот некоторые примеры из реальной жизни, которые обычно вызывают эту ошибку:

  • Общие службы
  • Синхронная трансляция событий
  • Создание динамического компонента

Каждый образец можно найти здесь here (plunkr), в моем случае проблема заключалась в создании динамического компонента.

Кроме того, по своему опыту я настоятельно рекомендую всем избегать решения setTimeout, в моем случае это вызвало "почти" бесконечный цикл (21 вызов, который я не хочу показывать вам, как их спровоцировать),

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

Возможно, вы делаете это неправильно, вы уверены, что правы?

В том же блоге также говорится:

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

Краткое руководство для меня заключается в рассмотрении по крайней мере следующих двух вещей при кодировании (я постараюсь дополнить его с течением времени):

  1. Избегайте изменения значений родительских компонентов от дочерних компонентов, вместо этого: измените их от своего родителя.
  2. При использовании директив @Input и @Output старайтесь избегать запуска изменений lyfecycle, если компонент полностью не инициализирован.
  3. Избегайте ненужных вызовов this.cdr.detectChanges();, они могут вызвать больше ошибок, особенно когда вы имеете дело с большим количеством динамических данных
  4. Когда использование this.cdr.detectChanges(); является обязательным, убедитесь, что используемые переменные (@Input, @Output, etc) заполнены/инициализированы с правой стороны обнаружения (OnInit, OnChanges, AfterView, etc)

Также

Если вы хотите полностью понять Angular Life Hook, я рекомендую вам прочитать официальную документацию здесь:

Ответ 9

Иногда я не могу заставить changeDetection() работать для меня. Обычно, когда элемент вида привязывается к компонентной функции, которая динамически устанавливает атрибут элемента, например <path [attr.d]="myPathFn()" /> или <div [style.left.px]="getPositionFn()"></div>.

Что я сделал в этих сценариях, это привязать данные атрибута элемента к переменной компонента и пересчитать эту переменную в одном из крючков жизненного цикла angular (ngDoCheck, ngOnChanges) или моей собственной функции setInterval().

вид

<path [attr.d]="myPath" />

компонент

myPath: string;

ngDoCheck() {
  this.myPath = this.myPathFn();
}

или

myPath: string;

// note: ngOnChanges wont fire on nested property changes of a complex input object
ngOnChanges() {
  this.myPath = this.myPathFn();
}

или

myPath: string;
myInterval: any;

ngAfterViewInit() {
  this.myInterval = setInterval(() => {
    this.myPath = this.myPathFn();
  }, 1000);
}

ngOnDestroy() {
    clearInterval(this.myInterval)
}