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

Resuelto Anthony Brenelière asked hace 8 años • 13 respuestas

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

lightnessAmpse calcula a partir de la fecha y hora actual. El color cambia si dtoDatees 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.

Anthony Brenelière avatar Sep 30 '16 15:09 Anthony Brenelière
Aceptado

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();
}
Günter Zöchbauer avatar Sep 30 '2016 08:09 Günter Zöchbauer

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 ( ngAfterViewCheckedpodría ser una alternativa).

Ejemplo : [ enlace ]


También

También las cosas asíncronas ngAfterViewInitque afectan el DOM pueden causar esto. También se puede resolver mediante setTimeouto 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

S Panfilov avatar Mar 15 '2018 14:03 S Panfilov

¡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.OnPushasí:

@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 ChangeDetectionStrategyfunciona.


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 setTimeoutsolució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 ):

  1. Evite modificar los valores de los componentes principales de los componentes secundarios; en su lugar, modifíquelos desde sus componentes principales.
  2. Cuando utilice directivas @Inputy @Outputtrate de evitar activar cambios en el ciclo de vida a menos que el componente esté completamente inicializado.
  3. 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.
  4. 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) .
  5. Cuando sea posible, elimine en lugar de ocultar , esto está relacionado con los puntos 3 y 4 ( la misma cita para angulardart )
  6. Evite cualquier tipo de lógica en el interior settersanotado con @Input, los configuradores se ejecutan previamente ngAfterViewInitpara que fácilmente desencadene el problema. En caso de que sea necesario, es mejor poner esa lógica dentro del ngOnChangesmétodo.

También

Si quieres entender completamente Angular Life Hook te recomiendo leer la documentación oficial aquí: https://angular.io/guide/lifecycle-hooks

luiscla27 avatar Mar 02 '2019 01:03 luiscla27

Como lo mencionó @leocaseiro sobre el tema de github .

Encontré 3 soluciones para aquellos que buscan soluciones fáciles.

1) Pasando de ngAfterViewInitangAfterContentInit

2) Pasar a ngAfterViewCheckedcombinado con ChangeDetectorRefcomo se sugiere en el n.° 14748 (comentario)

3) Continúe con ngOnInit() pero llame ChangeDetectorRef.detectChanges()después de sus cambios.

candidJ avatar Apr 05 '2018 07:04 candidJ