ngclass - ngstyle angular 5
Cómo gestionar Angular2 "la expresión ha cambiado después de haber sido verificada" cuando una propiedad de componente depende de la fecha y hora actual (8)
Aquí hay un pequeño extracto de la answer tomonari_t sobre las causas de este error, he intentado incluir solo las partes que me ayudaron a entender esto.
El artículo completo muestra ejemplos de código real sobre cada punto que se muestra aquí.
La causa raíz es el ciclo de vida angular:
Después de cada operación, Angular recuerda qué valores utilizó para realizar una operación. Se almacenan en la propiedad oldValues de la vista de componentes.
Después de que se hayan realizado las comprobaciones de todos los componentes, Angular inicia el siguiente ciclo de resumen, pero en lugar de realizar operaciones, compara los valores actuales con los que recuerda del ciclo de resumen anterior.
Las siguientes operaciones se verifican en el ciclo de resumen:
compruebe que los valores transmitidos a los componentes secundarios son los mismos que los valores que se usarían para actualizar las propiedades de estos componentes ahora.
compruebe que los valores utilizados para actualizar los elementos DOM son los mismos que los valores que se utilizarían para actualizar estos elementos ahora realizan lo mismo.
comprueba todos los componentes secundarios
Y así, el error se produce cuando los valores comparados son diferentes. , el blogger Max Koretskyi declaró:
El culpable es siempre el componente hijo o una directiva.
Y finalmente, aquí hay algunas muestras del mundo real que generalmente causan este error:
- Servicios compartidos
- Difusión de eventos síncronos
- Instanciación dinámica de componentes
Todas las muestras se pueden encontrar here (plunkr), en mi caso el problema era una instanciación dinámica de componentes.
Además, según mi propia experiencia, recomiendo a todos que eviten la solución
setTimeout
, en mi caso causó un bucle "casi" infinito (21 llamadas que no estoy dispuesto a mostrarles cómo provocarlas),
Recomendaría tener siempre en cuenta el ciclo de vida angular para que pueda tener en cuenta cómo se verían afectados cada vez que modifique el valor de otro componente. Con este error, Angular te dice:
Tal vez estás haciendo esto de la manera incorrecta, ¿estás seguro de que tienes razón?
El mismo blog también dice:
A menudo, la solución es usar el gancho de detección de cambio correcto para crear un componente dinámico
Una breve guía para mí es considerar al menos las siguientes dos cosas al codificar ( intentaré complementarlo con el tiempo ):
- Evite modificar los valores de los componentes principales a partir de sus componentes secundarios, en su lugar: modifíquelos desde su elemento primario.
-
Cuando use las directivas
@Input
y@Output
, trate de evitar desencadenar cambios de lyfecycle a menos que el componente se inicialice por completo.
Mi componente tiene estilos que dependen de la fecha y hora actual. En mi componente tengo la siguiente función.
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
se calcula a partir de la fecha y hora actual.
El color cambia si
dtoDate
está en las últimas 24 horas.
El error exacto es el siguiente:
La expresión ha cambiado después de que se verificó. Valor anterior: ''hsl (123, 80%, 49%)''. Valor actual: ''hsl (123, 80%, 48%)''
Sé que la excepción aparece en el modo de desarrollo solo en el momento en que se verifica el valor. Si el valor marcado es diferente del valor actualizado, se genera la excepción.
Así que intenté actualizar la fecha y hora actual en cada ciclo de vida en el siguiente método de enlace para evitar la excepción:
ngAfterViewChecked()
{
console.log( "! changement de la date du composant !" );
this.dateNow = new Date();
}
...pero sin éxito.
Como mencionó @leocaseiro en el tema de github .
Encontré 3 soluciones para aquellos que buscan soluciones fáciles.
1) Pasar de
ngAfterViewInit
angAfterContentInit
2) Pasar a
ngAfterViewChecked
combinado conChangeDetectorRef
como se sugiere en # 14748 (comentario)3) Mantenga con ngOnInit () pero llame a
ChangeDetectorRef.detectChanges()
después de sus cambios.
Creo que la mejor y más limpia solución que puedas imaginar es esta:
@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 }
);
}
}
Probado con Angular 5.2.9
Ejecute la detección de cambios explícitamente después del cambio:
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();
}
En nuestro caso, FIJAMOS agregando changeDetection en el componente y llama a detectChanges () en ngAfterContentChecked, código de la siguiente manera
@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();
}
}
Esta es una buena publicación para entender este error. Esto no es demasiado largo para leer.
Pequeña solución para este problema:
ngAfterViewInit() { // or ngOnInit or whatever
setTimeout(() => {
this.dateNow = new Date();
});
}
Un pequeño trabajo que usé muchas veces
Promise.resolve(null).then(() => {
console.log( "! changement de la date du composant !" );
this.dateNow = new Date();
this.cdRef.detectChanges();
});
Principalmente reemplazo "nulo" con alguna variable que uso en el controlador.