Cómo administrar la excepción "la expresión ha cambiado después de que se verificó" de Angular2 cuando la propiedad de un componente depende de la fecha y hora actual
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
es en las últimas 24 horas.
El error exacto es el siguiente:
La expresión ha cambiado después de ser verificada. 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 verificado es diferente del valor actualizado, se lanza la excepción.
Así que intenté actualizar la fecha y hora actual en cada ciclo de vida con 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.
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();
}
TL;DR
ngAfterViewInit() {
setTimeout(() => {
this.dateNow = new Date();
});
}
Aunque se trata de una solución alternativa, a veces es muy difícil resolver este problema de una manera más agradable, así que no se culpe si utiliza este enfoque. Esta bien.
Ejemplos : El problema inicial [ enlace ], resuelto con setTimeout()
[ enlace ]
Como evitar
En general, este error suele ocurrir después de agregar en algún lugar (incluso en componentes principales/secundarios) ngAfterViewInit
. Entonces, la primera pregunta es preguntarse: ¿puedo vivir sin él ngAfterViewInit
? Quizás muevas el código a alguna parte ( ngAfterViewChecked
podría ser una alternativa).
Ejemplo : [ enlace ]
También
También las cosas asíncronas ngAfterViewInit
que afectan el DOM pueden causar esto. También se puede resolver mediante setTimeout
o agregando el delay(0)
operador en la tubería:
ngAfterViewInit() {
this.foo$
.pipe(delay(0)) //"delay" here is an alternative to setTimeout()
.subscribe();
}
Ejemplo : [ enlace ]
Buena lectura
Buen artículo sobre cómo depurar esto y por qué sucede: enlace
¡Aquí tienes dos soluciones!
1. Modifique ChangeDetectionStrategy a OnPush
Para esta solución, básicamente le estás diciendo a angular:
Deje de buscar cambios; Lo haré sólo cuando sé que es necesario.
Modifique su componente para que se use ChangeDetectionStrategy.OnPush
así:
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
// ...
}
Con esto, las cosas ya no parecen funcionar. Esto se debe a que a partir de ahora tendrás que hacer que Angular llame detectChanges()
manualmente.
this.cdr.detectChanges();
Si estás interesado, consulta este artículo . Me ayudó a entender cómo ChangeDetectionStrategy
funciona.
2. Comprender el error ExpressionChangedAfterItHasBeenCheckedError
Consulte este video sobre el problema (¡es genial!). Además, aquí hay un pequeño extracto de este artículo sobre las causas de este error. He intentado incluir solo las partes que me ayudaron a comprenderlo.
El artículo completo muestra ejemplos de código real sobre cada punto que se muestra aquí.
La causa principal 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 del componente.
Una vez realizadas 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 los ciclos de resumen:
compruebe que los valores transmitidos a los componentes secundarios sean los mismos que los valores que se usarían para actualizar las propiedades de estos componentes ahora.
Verifique que los valores utilizados para actualizar los elementos DOM sean los mismos que los valores que se usarí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 bloguero Max Koretskyi afirmó:
El culpable es siempre el componente secundario o una directiva.
Y finalmente, aquí hay algunos ejemplos del mundo real que suelen causar este error:
- Servicios compartidos ( ejemplo )
- Transmisión de eventos sincrónicos ( ejemplo )
- Creación de instancias de componentes dinámicos ( ejemplo )
En mi caso, el problema fue la creación de instancias de un componente dinámico.
Además, desde mi propia experiencia, recomiendo encarecidamente a todos que eviten la setTimeout
solución, que en mi caso provocó 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 de Angular para que puedas tener en cuenta cómo se verían afectados cada vez que modifiques el valor de otro componente. Con este error Angular te está diciendo:
Quizás estés haciendo esto de manera incorrecta, ¿estás seguro de que tienes razón?
El mismo blog también dice:
A menudo, la solución es utilizar el gancho de detección de cambios correcto para crear un componente dinámico.
Una breve guía para mí es considerar al menos las siguientes cosas al codificar:
( Intentaré complementarlo con el tiempo ):
- Evite modificar los valores de los componentes principales de los componentes secundarios; en su lugar, modifíquelos desde sus componentes principales.
- Cuando utilice directivas
@Input
y@Output
trate de evitar activar cambios en el ciclo de vida a menos que el componente esté completamente inicializado. - Evite llamadas innecesarias, ya que
this.cdr.detectChanges();
pueden provocar más errores, especialmente cuando se trata de una gran cantidad de datos dinámicos. - Cuando el uso de
this.cdr.detectChanges();
es obligatorio, asegúrese de que las variables (@Input, @Output, etc
) que se utilizan estén completas/inicializadas en el gancho de detección correcto (OnInit, OnChanges, AfterView, etc
) . - Cuando sea posible, elimine en lugar de ocultar , esto está relacionado con los puntos 3 y 4 ( la misma cita para angulardart )
- Evite cualquier tipo de lógica en el interior
setters
anotado con@Input
, los configuradores se ejecutan previamentengAfterViewInit
para que fácilmente desencadene el problema. En caso de que sea necesario, es mejor poner esa lógica dentro delngOnChanges
método.
También
Si quieres entender completamente Angular Life Hook te recomiendo leer la documentación oficial aquí: https://angular.io/guide/lifecycle-hooks
Como lo mencionó @leocaseiro sobre el tema de github .
Encontré 3 soluciones para aquellos que buscan soluciones fáciles.
1) Pasando de
ngAfterViewInit
angAfterContentInit
2) Pasar a
ngAfterViewChecked
combinado conChangeDetectorRef
como se sugiere en el n.° 14748 (comentario)3) Continúe con ngOnInit() pero llame
ChangeDetectorRef.detectChanges()
después de sus cambios.