ExpressionChangedAfterItHasBeenCheckedError Explained

Explíqueme por qué sigo recibiendo este error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Obviamente, solo lo obtengo en modo dev, no ocurre en mi comstackción de producción, pero es muy molesto y simplemente no entiendo los beneficios de tener un error en mi entorno de desarrollo que no aparezca en prod – -probablemente debido a mi falta de comprensión.

Por lo general, la solución es bastante fácil, simplemente envuelvo el código que causa el error en un setTimeout como este:

 setTimeout(()=> { this.isLoading = true; }, 0); 

O fuerce a detectar cambios con un constructor como este: constructor(private cd: ChangeDetectorRef) {} :

 this.isLoading = true; this.cd.detectChanges(); 

Pero, ¿por qué constantemente encuentro este error? Quiero entenderlo para poder evitar estas soluciones hacky en el futuro.

Este error indica un problema real en su aplicación, por lo tanto, tiene sentido lanzar una excepción.

En devMode , la detección de cambios agrega un turno adicional después de cada ejecución de detección de cambios para verificar si el modelo ha cambiado.

Si el modelo ha cambiado entre el giro de detección de cambio regular y el cambio adicional, esto indica que

  • la detección del cambio en sí misma ha causado un cambio
  • un método o getter devuelve un valor diferente cada vez que se lo llama

los cuales son malos, porque no está claro cómo proceder porque el modelo podría nunca estabilizarse.

Si Angular se ejecuta, cambia la detección hasta que el modelo se estabilice, podría funcionar para siempre. Si Angular no ejecuta la detección de cambios, es posible que la vista no refleje el estado actual del modelo.

Consulte también ¿Cuál es la diferencia entre el modo de producción y el de desarrollo en Angular2?

Tuve un problema similar. Al ngAfterViewInit documentación de los ganchos del ciclo de vida , cambié ngAfterViewInit a ngAfterContentInit y funcionó.

Esto es más una nota al margen que una respuesta, pero podría ayudar a alguien. Me encontré con este problema cuando trato de hacer que la presencia de un botón dependa del estado de la forma:

  

Hasta donde yo sé, esta syntax lleva al botón que se agrega y elimina del DOM en función de la condición. Que a su vez conduce a ExpressionChangedAfterItHasBeenCheckedError .

La solución en mi caso (aunque no pretendo entender todas las implicaciones de la diferencia), fue usar display: none lugar:

  

Mucho de comprensión surgió una vez que entendí Angular Lifecycle Hooks y su relación con la detección de cambios.

Intentaba que Angular actualizara una bandera global vinculada al *ngIf de un elemento, y yo estaba tratando de cambiar ese indicador dentro del gancho del ciclo de vida ngOnInit() de otro componente.

Según la documentación, se llama a este método después de que Angular ya haya detectado cambios:

Se llama una vez, después de la primera ngOnChanges ().

Entonces la actualización del indicador dentro de ngOnChanges() no iniciará la detección de cambios. Luego, una vez que la detección de cambios se ha activado naturalmente de nuevo, el valor del indicador ha cambiado y se produce el error.

En mi caso, cambié esto:

 constructor(private globalEventsService: GlobalEventsService) { } ngOnInit() { this.globalEventsService.showCheckoutHeader = true; } 

A esto:

 constructor(private globalEventsService: GlobalEventsService) { this.globalEventsService.showCheckoutHeader = true; } ngOnInit() { } 

y solucionó el problema 🙂

En mi caso, tuve este problema en mi archivo de especificaciones, mientras ejecutaba mis pruebas.

Tuve que cambiar ngIf por [hidden]

  

a

  

Estaba enfrentando el mismo problema ya que el valor estaba cambiando en uno de los arreglos de mi componente. Pero en lugar de detectar los cambios en el cambio de valor, cambié la estrategia de detección de cambio de componente a onPush (que detectará los cambios en el cambio de objeto y no en el cambio de valor).

 import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush selector: - ...... }) 

Siga los pasos a continuación:

1. Use ‘ChangeDetectorRef’ importándolo de @ angular / core de la siguiente manera:

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

2. Implementarlo en constructor () de la siguiente manera:

 constructor( private cdRef : ChangeDetectorRef ) {} 

3. Agregue el siguiente método a su función a la que llama en un evento como hacer clic en un botón. Entonces se ve así:

 functionName() { yourCode; //add this line to get rid of the error this.cdRef.detectChanges(); } 

En referencia al artículo https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

Entonces, la mecánica detrás de la detección de cambios funciona de manera tal que ambos cambios en la detección y en la verificación se realizan de forma síncrona. Eso significa que, si actualizamos las propiedades de manera asincrónica, los valores no se actualizarán cuando se ejecute el bucle de verificación y no obtendremos ExpressionChanged... error. El motivo por el que obtenemos este error es que, durante el proceso de verificación, Angular ve valores diferentes a los que registró durante la fase de detección de cambios. Entonces para evitar eso …

1) Use changeDetectorRef

2) usa setTimeOut. Esto ejecutará su código en otra máquina virtual como una macro tarea. Angular no verá estos cambios durante el proceso de verificación y no obtendrá ese error.

  setTimeout(() => { this.isLoading = true; }); 

3) Si realmente quieres ejecutar tu código en la misma VM, usa como

 Promise.resolve(null).then(() => this.isLoading = true); 

Esto creará una micro-tarea. La cola de micro-tareas se procesa después de que el código síncrono actual haya terminado de ejecutarse, por lo que la actualización de la propiedad se realizará después del paso de verificación.

Tuve este tipo de error en Ionic3 (que usa Angular 4 como parte de su stack de tecnología).

Para mí fue hacer esto:

Así que estaba tratando de cambiar de forma condicional el tipo de ícono de iones desde un pin a un remove-circle , por un modo en el que estaba operando una pantalla.

Supongo que tendré que agregar un * ngIF en su lugar.

Para mi problema, estaba leyendo github – “ExpressionChangedAfterItHasBeenCheckedError al cambiar el valor de un componente ‘no modelo’ en afterViewInit” y decidí agregar el ngModel

  

Solucionó mi problema, espero que ayude a alguien.

Aquí está mi opinión sobre lo que está sucediendo. No he leído la documentación, pero estoy seguro de que esto es parte de por qué se muestra el error.

 *ngIf="isProcessing()" 

Al usar * ngIf, cambia físicamente el DOM al agregar o eliminar el elemento cada vez que cambia la condición. Por lo tanto, si la condición cambia antes de que se represente en la vista (lo que es muy posible en el mundo de Angular), se produce el error. Vea la explicación aquí entre los modos de desarrollo y producción.

 [hidden]="isProcessing()" 

Al usar [oculto] no cambia físicamente el DOM, sino que simplemente oculta el elemento de la vista, lo más probable es que utilice CSS en la parte posterior. El elemento todavía está allí en el DOM pero no es visible según el valor de la condición. Es por eso que el error no ocurrirá al usar [oculto].

@HostBinding puede ser una fuente confusa de este error.

Por ejemplo, supongamos que tiene el siguiente enlace de host en un componente

 // image-carousel.component.ts @HostBinding('style.background') style_groupBG: string; 

Para simplificar, digamos que esta propiedad se actualiza a través de la siguiente propiedad de entrada:

 @Input('carouselConfig') public set carouselConfig(carouselConfig: string) { this.style_groupBG = carouselConfig.bgColor; } 

En el componente principal, lo configura programáticamente en ngAfterViewInit

 @ViewChild(ImageCarousel) carousel: ImageCarousel; ngAfterViewInit() { this.carousel.carouselConfig = { bgColor: 'red' }; } 

Esto es lo que sucede:

  • Tu componente padre es creado
  • El componente ImageCarousel se crea y se asigna al carousel (a través de ViewChild)
  • No podemos acceder al carousel hasta ngAfterViewInit() (será nulo)
  • style_groupBG = 'red' la configuración, que establece style_groupBG = 'red'
  • Esto a su vez establece el background: red en el componente Host ImageCarousel
  • Este componente es ‘propiedad’ de su componente principal, por lo que cuando busca cambios encuentra un cambio en carousel.style.background y no es lo suficientemente inteligente como para saber que esto no es un problema, por lo que arroja la excepción.

Una solución es introducir otro wrapper div insider ImageCarousel y establecer el color de fondo sobre eso, pero luego no obtiene algunos de los beneficios de usar HostBinding (como permitir que el padre controle los límites completos del objeto).

La mejor solución, en el componente principal, es agregar detectChanges () después de configurar la configuración.

 ngAfterViewInit() { this.carousel.carouselConfig = { ... }; this.cdr.detectChanges(); } 

Esto puede parecer bastante obvio establecerse de esta manera, y muy similar a otras respuestas, pero hay una diferencia sutil.

Considere el caso donde no agrega @HostBinding hasta más tarde durante el desarrollo. De repente, obtienes este error y no parece tener ningún sentido.

Consejos para depurar

Este error puede ser bastante confuso, y es fácil hacer una suposición errónea acerca de cuándo ocurre exactamente. Me parece útil agregar muchas declaraciones de depuración como esta en todos los componentes afectados en los lugares apropiados. Esto ayuda a entender el flujo.

En las declaraciones padre de este tipo (la cadena exacta ‘EXPRESSIONCHANGED’ es importante), pero aparte de eso, estos son solo ejemplos:

  console.log('EXPRESSIONCHANGED - HomePageComponent: constructor'); console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig); console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok'); console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges'); 

En el niño / servicios / devoluciones de llamada del temporizador:

  console.log('EXPRESSIONCHANGED - ChildComponent: setting config'); console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok'); 

Si ejecuta detectChanges agregue manualmente el registro para eso también:

  console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges'); this.cdr.detectChanges(); 

Luego, en el depurador de Chrome solo filtra por ‘EXPRESSIONCHANGES’. Esto le mostrará exactamente el flujo y el orden de todo lo que se establece, y también exactamente en qué punto Angular arroja el error.

enter image description here

También puede hacer clic en los enlaces grises para colocar los puntos de interrupción.

Otra cosa que debe tenerse en cuenta si tiene propiedades de nombre similar en toda su aplicación (como style.background ) asegúrese de que está depurando la que usted piensa, configurándola en un valor de color oscuro.