previous - expressionchangedafterithasbeencheckederror angular 6
ExpressionChangedAfterItHasBeenCheckedError Explicado (22)
Consejos de depuración
Este error puede ser bastante confuso, y es fácil hacer una suposición errónea sobre cuándo exactamente está ocurriendo. Encuentro ú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 el padre poner declaraciones como esta (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 las devoluciones de llamadas de niño / servicios / 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, simplemente filtre 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.
También puede hacer clic en los enlaces grises para colocar puntos de interrupción.
Otra cosa a tener en cuenta si tiene propiedades con nombres similares en toda su aplicación (como
style.background
) asegúrese de depurar la que cree, configurándola en un valor de color oscuro.
Explíqueme por qué sigo recibiendo este error:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
Obviamente, solo lo obtengo en modo de desarrollo, no sucede en mi versió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 aparecerá en prod. -probablemente debido a mi falta de comprensión.
Por lo general, la solución es bastante fácil, solo envuelvo el error que causa el código en un setTimeout como este:
setTimeout(()=> {
this.isLoading = true;
}, 0);
O forzar la detección de cambios con un constructor como este:
constructor(private cd: ChangeDetectorRef) {}
:
this.isLoading = true;
this.cd.detectChanges();
Pero, ¿por qué me encuentro constantemente con este error? Quiero entenderlo para poder evitar estas soluciones hacky en el futuro.
@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 mediante la siguiente propiedad de entrada:
@Input(''carouselConfig'')
public set carouselConfig(carouselConfig: string)
{
this.style_groupBG = carouselConfig.bgColor;
}
En el componente principal, lo está configurando programáticamente en
ngAfterViewInit
@ViewChild(ImageCarousel) carousel: ImageCarousel;
ngAfterViewInit()
{
this.carousel.carouselConfig = { bgColor: ''red'' };
}
Esto es lo que pasa:
- Se crea su componente principal
-
El componente ImageCarousel se crea y se asigna al
carousel
(a través de ViewChild) -
No podemos acceder al
carousel
hastangAfterViewInit()
(será nulo) -
style_groupBG = ''red''
la configuración, que establecestyle_groupBG = ''red''
-
Esto a su vez establece el
background: red
en el componente ImageCarousel del host -
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 contenedor de información privilegiada ImageCarousel y establecer el color de fondo en eso, pero luego no obtendrá 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 como este, y muy similar a otras respuestas, pero hay una sutil diferencia.
Considere el caso en el que no agrega
@HostBinding
hasta más tarde durante el desarrollo.
De repente, obtienes este error y no parece tener ningún sentido.
Angular ejecuta la detección de cambios y cuando encuentra que algunos valores que se han pasado al componente secundario han cambiado Angular arroja el error ExpressionChangedAfterItHasBeenCheckedError, https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
Para corregir esto, podemos usar el gancho del ciclo de vida AfterContentChecked y
import { ChangeDetectorRef, AfterContentChecked} from ''@angular/core'';
constructor(
private cdref: ChangeDetectorRef) { }
ngAfterContentChecked() {
this.cdref.detectChanges();
}
Aquí están mis pensamientos 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()"
Cuando se usa * ngIf, cambia físicamente el DOM al agregar o eliminar el elemento cada vez que cambia la condición. Entonces, si la condición cambia antes de que se muestre en la vista (lo cual es muy posible en el mundo de Angular), se produce el error. Vea la explicación here entre los modos de desarrollo y producción.
[hidden]="isProcessing()"
Cuando se usa [oculto] no cambia físicamente el DOM, sino que simplemente oculta el elemento de la vista, lo más probable es que use CSS en la parte posterior. El elemento todavía está allí en el DOM pero no es visible dependiendo del valor de la condición. Es por eso que el error no ocurrirá al usar [oculto].
En mi caso, tenía una propiedad asíncrona en
LoadingService
con un BehavioralSubject
isLoading
El uso del modelo [oculto] funciona, pero * ngIf falla
<h1 [hidden]="!(loaderService.isLoading | async)">
THIS WORKS FINE
(Loading Data)
</h1>
<h1 *ngIf="!(loaderService.isLoading | async)">
THIS THROWS ERROR
(Loading Data)
</h1>
En mi caso, tuve este problema en mi archivo de especificaciones, mientras ejecutaba mis pruebas.
Tuve que cambiar
ngIf
a
[hidden]
<app-loading *ngIf="isLoading"></app-loading>
a
<app-loading [hidden]="!isLoading"></app-loading>
En referencia al artículo https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
Por lo tanto, la mecánica detrás de la detección de cambios en realidad funciona de manera que tanto los resúmenes de detección de cambios como los de verificación se realizan sincrónicamente.
Eso significa que si actualizamos las propiedades de forma asincrónica, los valores no se actualizarán cuando se ejecute el bucle de verificación y no obtendremos el error
ExpressionChanged...
La razón por la que obtenemos este error es que, durante el proceso de verificación, Angular ve valores diferentes de 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 VM 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 desea ejecutar su código en la misma VM, use como
Promise.resolve(null).then(() => this.isLoading = true);
Esto creará una microtarea. La cola de micro tareas se procesa después de que el código síncrono actual ha terminado de ejecutarse, por lo tanto, la actualización de la propiedad se realizará después del paso de verificación.
Espero que esto ayude a alguien que viene aquí: Realizamos llamadas de servicio en
ngOnInit
de la siguiente manera y usamos una variable
displayMain
para controlar el Montaje de los elementos en el DOM.
componente.ts
displayMain: boolean;
ngOnInit() {
this.displayMain = false;
// Service Calls go here
// Service Call 1
// Service Call 2
// ...
this.displayMain = true;
}
y component.html
<div *ngIf="displayMain"> <!-- This is the Root Element -->
<!-- All the HTML Goes here -->
</div>
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 componentes a
onPush
(que detectará 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: -
......
})
Estaba teniendo un bloque de código en el alcance de suscripción, simplemente lo cambié al alcance superior y funciona bien.
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 regular para verificar si el modelo ha cambiado.
Si el modelo ha cambiado entre el turno de detección de cambio normal y el adicional, esto indica que
- la detección de cambios en sí ha causado un cambio
- un método o getter devuelve un valor diferente cada vez que se llama
ambos son malos, porque no está claro cómo proceder porque el modelo podría nunca estabilizarse.
Si Angular ejecuta la detección de cambios hasta que el modelo se estabilice, podría ejecutarse para siempre. Si Angular no ejecuta la detección de cambios, entonces la vista podría no reflejar el estado actual del modelo.
Consulte también ¿Cuál es la diferencia entre el modo de producción y desarrollo en Angular2?
Hubo respuestas interesantes, pero no pareció encontrar una que se ajustara a mis necesidades, siendo la más cercana de @ chittrang-mishra que se refiere solo a una función específica y no a varios conmutadores como en mi aplicación.
No quería usar
[hidden]
para aprovechar
*ngIf
ni siquiera es parte del DOM, encontré la siguiente solución, que puede no ser la mejor para todos, ya que suprime el error en lugar de corregirlo, pero en mi Si sé que el resultado final es correcto, parece correcto para mi aplicación.
Lo que hice fue implementar
AfterViewChecked
, agregar
constructor(private changeDetector : ChangeDetectorRef ) {}
y luego
functionName() {
yourCode;
//add this line to get rid of the error
this.cdRef.detectChanges();
}
Espero que esto ayude a otros como muchos otros me han ayudado.
La solución ... servicios y rxjs ... los emisores de eventos y el enlace de propiedad usan rxjs ... es mejor implementarlo usted mismo, más control, más fácil de depurar. Recuerde que los emisores de eventos están utilizando rxjs. Simplemente, cree un servicio y dentro de un observable, haga que cada componente se suscriba al observador y pase un nuevo valor o un valor de costo según sea necesario
Mi problema se manifestó cuando agregué
*ngIf
pero esa no fue la causa.
El error fue causado al cambiar el modelo en las etiquetas
{{}}
luego al intentar mostrar el modelo modificado en la declaración
*ngIf
más adelante.
Aquí hay un ejemplo:
<div>{{changeMyModelValue()}}</div> <!--don''t do this! or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>
Para solucionar el problema, cambié donde llamé changeMyModelValue () a un lugar que tenía más sentido.
En mi situación, quería que se llamara a changeMyModelValue () cada vez que un componente hijo cambiaba los datos. Esto requirió que creara y emitiera un evento en el componente hijo para que el padre pudiera manejarlo (llamando a changeMyModelValue (). Consulte https://angular.io/guide/component-interaction#parent-listens-for-child-event
Mucho entendimiento llegó una vez que entendí los Angular Lifecycle Hooks y su relación con la detección de cambios.
Estaba tratando de hacer que Angular actualizara un indicador global vinculado al
*ngIf
de un elemento, y 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:
Llamado una vez, después de los primeros ngOnChanges ().
Por lo tanto, actualizar el indicador dentro de
ngOnChanges()
no iniciará la detección de cambios.
Luego, una vez que la detección de cambios se ha activado naturalmente nuevamente, el valor de la bandera 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 :)
Para mi problema, estaba leyendo github - "ExpressionChangedAfterItHasBeenCheckedError al cambiar el valor de un componente ''no modelo'' en afterViewInit" y decidí agregar el ngModel
<input type="hidden" ngModel #clientName />
Solucionó mi problema, espero que ayude a alguien.
Recibí este error porque estaba enviando acciones redux en modal y modal no estaba abierto en ese momento. Estaba enviando acciones en el momento en que el componente modal recibe la entrada. Así que puse setTimeout allí para asegurarme de que se abre modal y luego se dipatched las acciones.
Recibí este error porque estaba usando una variable en component.html que no se declaró en component.ts. Una vez que eliminé la parte en HTML, este error desapareció.
Siga los pasos a continuación:
1. Use ''ChangeDetectorRef'' importándolo de @ angular / core de la siguiente manera:
ngAfterViewChecked(){
this.changeDetector.detectChanges();
}
2. Impleméntelo en constructor () de la siguiente manera:
import{ ChangeDetectorRef } from ''@angular/core'';
3. Agregue el siguiente método a su función que está llamando en un evento como hacer clic en el botón. Entonces se ve así:
constructor( private cdRef : ChangeDetectorRef ) {}
Tuve este tipo de error en Ionic3 (que usa Angular 4 como parte de su pila de tecnología).
Para mí estaba haciendo esto:
<ion-icon [name]="getFavIconName()"></ion-icon>
Así que estaba tratando de cambiar condicionalmente el tipo de un
ion-icon
de
ion-icon
de un
pin
a un
remove-circle
, según el modo en que funcionaba una pantalla.
Supongo que tendré que agregar un * ngIF en su lugar.
Tuve un problema similar.
Mirando la
documentación de los ganchos
del
ciclo de vida
, cambié
ngAfterViewInit
a
ngAfterContentInit
y funcionó.
Actualizar
Recomiendo encarecidamente comenzar primero con la
respuesta automática del OP
: piense correctamente en lo que se puede hacer en el
constructor
frente a lo que se debe hacer en
ngOnChanges()
.
Original
Esto es más una nota al margen que una respuesta, pero podría ayudar a alguien. Me topé con este problema al intentar hacer que la presencia de un botón dependiera del estado del formulario:
<button *ngIf="form.pristine">Yo</button>
Hasta donde sé, esta sintaxis hace que el botón se agregue y elimine del DOM según la condición.
Lo que a su vez conduce a
ExpressionChangedAfterItHasBeenCheckedError
.
La solución en mi caso (aunque no pretendo comprender todas las implicaciones de la diferencia), fue usar
display: none
lugar:
<button [style.display]="form.pristine ? ''inline'' : ''none''">Yo</button>