Как управлять выражением 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();
}
... но безуспешно.
Ответы
Ответ 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();
}
Ответ 2
Это хорошая статья, чтобы понять эту ошибку. Это не слишком долго читать.
Ответ 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 говорит вам:
Возможно, вы делаете это неправильно, вы уверены, что правы?
В том же блоге также говорится:
Часто исправление заключается в использовании правильного хука обнаружения изменений для создания динамического компонента
Краткое руководство для меня заключается в рассмотрении по крайней мере следующих двух вещей при кодировании (я постараюсь дополнить его с течением времени):
- Избегайте изменения значений родительских компонентов от дочерних компонентов,
вместо этого: измените их от своего родителя.
- При использовании директив
@Input
и @Output
старайтесь избегать запуска изменений lyfecycle, если компонент полностью не инициализирован.
- Избегайте ненужных вызовов
this.cdr.detectChanges();
, они могут вызвать больше ошибок, особенно когда вы имеете дело с большим количеством динамических данных
- Когда использование
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)
}
Ответ 10
проверьте эту статью, это может помочь вам, ребята, понять причину ошибки и как ее исправить. https://blog.angular-university.io/angular-debugging/