typescript - previous - expressionchangedafterithasbeencheckederror ngif
La expresión___ ha cambiado después de que se verificó (12)
¿Por qué es el componente en este simple plunk
@Component({
selector: ''my-app'',
template: `<div>I''m {{message}} </div>`,
})
export class App {
message:string = ''loading :('';
ngAfterViewInit() {
this.updateMessage();
}
updateMessage(){
this.message = ''all done loading :)''
}
}
lanzamiento:
EXCEPCIÓN: La expresión ''I''m {{message}} en la aplicación @ 0: 5'' ha cambiado después de que se verificó. Valor anterior: ''Estoy cargando :(''. Valor actual: ''Ya terminé de cargar :)'' en [I''m {{message}} en App @ 0: 5]
cuando todo lo que estoy haciendo es actualizar un enlace simple cuando se inicia mi vista?
¿No puede usar
ngOnInit
porque solo está cambiando el
message
variable miembro?
Si desea acceder a una referencia a un componente hijo
@ViewChild(ChildComponent)
, debe esperar con
ngAfterViewInit
.
Una solución sucia es llamar a
updateMessage()
en el siguiente bucle de eventos con, por ejemplo, setTimeout.
ngAfterViewInit() {
setTimeout(() => {
this.updateMessage();
}, 1);
}
Como lo indicó drewmoore, la solución adecuada en este caso es activar manualmente la detección de cambios para el componente actual.
Esto se realiza utilizando el método
detectChanges()
del objeto
ChangeDetectorRef
(importado de
angular2/core
), o su método
markForCheck()
, que también actualiza los componentes principales.
example
relevante:
import { Component, ChangeDetectorRef, AfterViewInit } from ''angular2/core''
@Component({
selector: ''my-app'',
template: `<div>I''m {{message}} </div>`,
})
export class App implements AfterViewInit {
message: string = ''loading :('';
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.message = ''all done loading :)''
this.cdr.detectChanges();
}
}
Aquí también hay Plunkers que demuestran los ngOnInit , setTimeout y enableProdMode si acaso.
El artículo
Todo lo que necesita saber sobre el error
ExpressionChangedAfterItHasBeenCheckedError
explica el comportamiento con gran detalle.
El problema con su configuración es que el
ngAfterViewInit
ciclo de vida
ngAfterViewInit
se ejecuta después de las actualizaciones DOM procesadas de detección de cambios.
Y está cambiando efectivamente la propiedad que se utiliza en la plantilla en este enlace, lo que significa que DOM debe volver a representarse:
ngAfterViewInit() {
this.message = ''all done loading :)''; // needs to be rendered the DOM
}
y esto requerirá otro ciclo de detección de cambio y Angular por diseño solo ejecuta un ciclo de resumen.
Básicamente tienes dos alternativas para solucionarlo:
-
setTimeout
la propiedad asincrónicamente usandosetTimeout
,Promise.then
o asincrónica observable referenciada en la plantilla -
realice la actualización de la propiedad en un enlace antes de la actualización DOM: ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.
Lo arreglé agregando ChangeDetectionStrategy desde el núcleo angular.
import { Component, ChangeDetectionStrategy } from ''@angular/core'';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: ''page1'',
templateUrl: ''page1.html'',
})
No pude comentar sobre la publicación de @Biranchi ya que no tengo suficiente reputación, pero me solucionó el problema.
Una cosa a tener en cuenta! Si agregar changeDetection: ChangeDetectionStrategy.OnPush en el componente no funcionó, y es un componente hijo (componente tonto), intente agregarlo también al padre.
Esto solucionó el error, pero me pregunto cuáles son los efectos secundarios de esto.
Para esto, he intentado las respuestas anteriores, muchas no funcionan en la última versión de Angular (6 o posterior)
Estoy usando el control de material que requirió cambios después de realizar el primer enlace.
export class AbcClass implements OnInit, AfterContentChecked{
constructor(private ref: ChangeDetectorRef) {}
ngOnInit(){
// your tasks
}
ngAfterViewInit() {
this.ref.detectChanges();
}
}
Agregando mi respuesta, esto ayuda a algunos a resolver problemas específicos.
Primero, tenga en cuenta que esta excepción solo se lanzará cuando esté ejecutando su aplicación en modo de desarrollo (que es el caso de forma predeterminada a partir de beta-0): si llama a
enableProdMode()
al
enableProdMode()
la aplicación, no se obtendrá arrojado (
ver plunk actualizado
).
En segundo lugar, no haga eso porque esta excepción se está lanzando por una buena razón: en resumen, cuando está en modo dev, cada ronda de detección de cambio es seguida inmediatamente por una segunda ronda que verifica que no hayan cambiado los enlaces desde el final de la primera, como esto indicaría que los cambios están siendo causados por la detección de cambios en sí.
En su plunk, el enlace
{{message}}
es cambiado por su llamada a
setMessage()
, lo que ocurre en el
ngAfterViewInit
, que ocurre como parte del turno de detección de cambio inicial.
Sin embargo, eso en sí mismo no es problemático: el problema es que
setMessage()
cambia el enlace pero no desencadena una nueva ronda de detección de cambios, lo que significa que este cambio no se detectará hasta que se active alguna futura ronda de detección de cambios en otro lugar .
La conclusión: cualquier cosa que cambie un enlace debe activar una ronda de detección de cambio cuando lo haga.
Actualice en respuesta a todas las solicitudes de un ejemplo de cómo hacerlo : la solución de @ Tycho funciona, al igual que los tres métodos en la respuesta que señaló @MarkRajcok. Pero, francamente, todos se sienten feos y equivocados para mí, como el tipo de trucos a los que nos acostumbramos en ng1.
Sin duda, hay circunstancias ocasionales en las que estos hacks son apropiados, pero si los está utilizando de una forma más que ocasional, es una señal de que está luchando contra el marco en lugar de adoptar plenamente su naturaleza reactiva.
En mi humilde opinión, una forma más idiomática, "Angular2" de abordar esto es algo así como: ( plunk )
@Component({
selector: ''my-app'',
template: `<div>I''m {{message | async}} </div>`
})
export class App {
message:Subject<string> = new BehaviorSubject(''loading :('');
ngAfterViewInit() {
this.message.next(''all done loading :)'')
}
}
Solo tiene que actualizar su mensaje en el
ngAfterContentChecked
correcto del ciclo de vida, en este caso es
ngAfterContentChecked
lugar de
ngAfterViewInit
, porque en ngAfterViewInit se ha iniciado una comprobación del mensaje variable pero aún no ha finalizado.
ver: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview
entonces el código será solo:
import { Component } from ''angular2/core''
@Component({
selector: ''my-app'',
template: `<div>I''m {{message}} </div>`,
})
export class App {
message: string = ''loading :('';
ngAfterContentChecked() {
this.message = ''all done loading :)''
}
}
vea la demostración de trabajo en Plunker.
También puede crear un temporizador usando la función rxjs
Observable.timer
y luego actualizar el mensaje en su suscripción:
Observable.timer(1).subscribe(()=> this.updateMessage());
También puede poner su llamada a updateMessage () en el método ngOnInt (), al menos funciona para mí
ngOnInit() {
this.updateMessage();
}
En RC1 esto no desencadena la excepción
ngAfterViewChecked()
funcionó para mí:
import { Component, ChangeDetectorRef } from ''@angular/core''; //import ChangeDetectorRef
constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
//your code to update the model
this.cdr.detectChanges();
}
Lanza un error porque su código se actualiza cuando se llama a ngAfterViewInit () . Significa que su valor inicial cambió cuando se produce ngAfterViewInit. Si llama a eso en ngAfterContentInit () , no arrojará un error.
ngAfterContentInit() {
this.updateMessage();
}