«Expression has changed after it was checked» — ¿por qué ocurre y cómo vivir con ello?
\n\nSi alguna vez has escrito una aplicación en Angular, seguramente te has topado con este siniestro mensaje en la consola:
\n\nERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.\n\nEste error es un verdadero «azote» para los desarrolladores. Miles de preguntas en Stack Overflow, cientos de horas de depuración, decenas de crisis nerviosas. Pero la buena noticia es: no solo se puede corregir, sino también entender por qué ocurre. En este artículo analizaremos las principales causas del error Expression has changed after it was checked, escribiremos ejemplos en TypeScript y daremos soluciones concretas. ¡Allá vamos!
\n\n¿Qué significa realmente este error?
\n\nAngular funciona en dos modos: desarrollo (dev mode) y producción. En el modo dev, el framework realiza una verificación adicional: después de cada ciclo de detección de cambios, ejecuta una segunda pasada para asegurarse de que los datos no hayan cambiado «sobre la marcha». Si el valor cambió entre la primera y la segunda pasada, Angular falla con el error ExpressionChangedAfterItHasBeenCheckedError.
\n\nEn palabras simples: le dijiste a Angular «estos son los datos», los verificó, y luego los cambiaste. Angular se ofende y grita.
\n\nLas 3 principales causas del error
\n\n1. Cambio de datos en hooks del ciclo de vida después de ngAfterViewInit
\n\nLa causa más popular es intentar cambiar una propiedad del componente en el hook ngAfterViewInit, ngAfterContentInit o ngAfterViewChecked. En ese momento, Angular ya ha completado la verificación de la vista, y cualquier cambio provoca el error.
Ejemplo en TypeScript:
\n\nimport { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n selector: 'app-example',\n template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n message = 'Valor inicial';\n\n ngAfterViewInit(): void {\n // ¡Error! Cambiamos la propiedad después de verificar la vista\n this.message = 'Valor modificado';\n }\n}\n\nAquí Angular primero verifica message (valor «Valor inicial»), luego llama a ngAfterViewInit, donde lo cambias a «Valor modificado». En la segunda verificación, Angular ve la discrepancia y lanza el error.
2. Operaciones asíncronas dentro del ciclo de vida
\n\nSi ejecutas código asíncrono (setTimeout, Promise, Observable) dentro de un hook, la actualización de datos ocurre fuera de la zona de control de Angular.
\n\nEjemplo:
\n\nimport { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'app-async',\n template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n data = 'Inicio';\n\n ngOnInit(): void {\n setTimeout(() => {\n this.data = 'Nuevo valor';\n }, 0);\n }\n}\n\nAunque setTimeout con retardo de 0 ms se ejecuta casi de inmediato, Angular ya ha completado la verificación. En modo dev, esto provocará el error.
\n\n3. Componentes anidados y paso de datos mediante Input
\n\nCuando el componente padre pasa datos al hijo mediante @Input, y el componente hijo intenta cambiar esos datos en su ciclo de vida, surge un conflicto.
Ejemplo:
\n\n// Componente padre\n@Component({\n selector: 'app-parent',\n template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n parentValue = 'Original';\n}\n\n// Componente hijo\n@Component({\n selector: 'app-child',\n template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n @Input() value: string = '';\n\n ngAfterViewInit(): void {\n this.value = 'Modificado en