Angular: ExpressionChangedAfterItHasBeenCheckedError: la expresión ha cambiado después de que se verificó en ng 4

Creado en 16 jun. 2017  ·  201Comentarios  ·  Fuente: angular/angular

Estoy enviando un ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Comportamiento actual


Después de usar un objeto observable en mi plantilla usando async, estoy recibiendo:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: La expresión ha cambiado después de que se verificó. Valor anterior: ''. Valor actual: 'algo'

Comportamiento esperado

Háblenos de su entorno

<br i="15"/>
Angular version: 4.2.2

<p i="16">Browser:</p>

<ul i="17">
<li i="18">[X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)</li>
</ul>

<p i="19">For Tooling issues:</p>

<ul i="20">
<li i="21">Node version: v6.10.3</li>
<li i="22">Platform: Mac</li>
</ul>
core regression bufix

Comentario más útil

Tengo el mismo problema después de actualizar de 4.1.3 a 4.2.3. El uso de setTimeout soluciona el problema.

desde:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

a:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

Todos 201 comentarios

Tuve este mismo problema con el uso de redux. Aquí hay un enlace al problema que publiqué en su repositorio. Cuando en angular 2 no hay errores, pero en angular 4.2.2 se cambia la expresión después de que se verificaron los errores.
https://github.com/angular-redux/store/issues/428

El problema me aparece en mis componentes al pasar de 4.1.3 a 4.2.x. Volver a 4.1.3 me soluciona el problema, pero eso no es algo que quiera conservar para siempre.

Podría reproducir en un proyecto mínimo, todavía estoy tratando de averiguar cómo solucionar esta (tal vez) regresión. Pero parece que el cheque sucio se ha aplicado en dev. modo desde 4.2.x. Pero el registro de cambios no es claro al respecto. Cualquier propina bienvenida.

Tengo el mismo problema después de actualizar de 4.1.3 a 4.2.3. El uso de setTimeout soluciona el problema.

desde:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

a:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

@jingglang, ¿ podría intentar reproducir el problema en http://plnkr.co/edit/tpl : AvJOMERrnz94ekVua0u5 con el menor código posible?

Tengo el mismo problema: actualizado de 2.4 a 4.2, y parte de mi lógica de ocultar / mostrar ahora está rota. Es una lógica de estilo acordeón, donde la visibilidad depende de comparar el panel actual con un observable que contiene qué panel debería ser visible (entre otras cosas). El componente usa @select para obtener el observable y está vinculado con | async en la plantilla: funcionó como un encanto en 2.4, ahora obtenga ExpressionChangedAfterItHasBeenCheckedError y los paneles no se ocultan ni se muestran en el primer clic, solo en el segundo.

Tengo el mismo problema cuando actualizo 4.1.3 a 4.2.2.

Pero encontré una solución.
Inyección de ChangeDetectionRef , y llame a la función detectionChanges() en el punto de error.

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

Creo que este es un problema con el siguiente cambio realizado en 4.2.

https://github.com/angular/angular/pull/16592

para resolver este problema: https://github.com/angular/angular/issues/14321

¿Tiene algún contenido dentro de una etiqueta ng-content ?

Tengo el mismo problema con un formulario dentro de una etiqueta ng-content que ahora presenta errores con el error ContentChangedAfter.

Tengo el mismo problema cuando actualizo 4.1.3 a 4.2.0 o superior

Adjunto un pequeño proyecto para reproducir el error.

app-error.zip

A continuación, bajé la calificación a 4.1.3 (como se indicó anteriormente) y el error desaparece, por lo que sea cual sea el conflicto, es específico de 4.2. No tengo tiempo para armar un plunkr esta semana (o la próxima), pero parece estar relacionado con una sola acción que actualiza dos observables separados en la tienda, cada uno de los cuales tiene un impacto en la interfaz de usuario. Se produce una de las actualizaciones de la interfaz de usuario, la otra provoca la excepción.

Hmmm,
Definitivamente, la reversión a la versión 4.1.3 resuelve el problema y también aplica el tiempo de espera como lo muestra @jingglang.

hola @tytskyi. Hice un plunkr http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT para este problema. espero que ayude.

la solución proporcionada por @jingglang funciona (para mí) en 4.2.0 o superior
O también cambiar el evento emisor del niño en su constructor ya no genera el error

@umens Actualiza el componente principal después de que se verificó, por lo tanto, no mantiene el flujo de datos unidireccional

@alexzuza ¿cómo puedo lograrlo? Siempre lo he hecho así sin el error. Por qué poner SetTimout ya no genera el error. (Actualicé el plunkr) Dudo que esté interfiriendo con el ciclo de vida o ¿no?

hola @umens gracias por tu tiempo. El problema es lo que dice @alexzuza : actualiza el campo del componente principal después de que se verificó. Su código es aproximadamente equivalente a esto: http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm?p=preview (observe que eliminé el servicio para reducir el código).
¿Por qué funcionó? Probablemente, por accidente, la versión anterior de angular o Rxjs tenía un error aquí. ¿Podría decirnos qué versiones se utilizaron? ¿O incluso ponerlo en funcionamiento?

Por qué poner SetTimout ya no genera el error.

Porque cambia de campo de forma asincrónica, lo que respeta el flujo de datos unidireccional.

Considérelo desde lo contrario: ¿Por qué se genera el error? La primera comprobación de la detección de cambios va de arriba a abajo. Comprueba el app.toolsConfig vinculado a la plantilla. Luego muestra <child-cmp> que actualiza sincrónicamente app.toolsConfig . La renderización está lista. Ahora ejecuta la segunda verificación (solo en modo dev) de la detección de cambios para garantizar que el estado de la aplicación sea estable. Pero app.toolsConfig antes de representar al niño no es igual a app.toolsConfig después. Todo esto sucede durante un solo "turno", "ciclo" de detección de cambios.

Okay. Gracias por la respuesta detallada. No pude encontrar información sobre cuándo ocurre el ciclo de vida del componente secundario.
para la versión anterior "de trabajo" que utilicé:
@ angular / *: 4.1.1 (excepto compilador-cli -> 4.1.0)
rxjs: 5.3.1

@umens aquí se reproduce un error con las versiones anteriores que mencionó: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview. Lanza, entonces, ¿probablemente algún comportamiento afectado por dependencia de terceros o no completa la instrucción de reproducción?

no puedo decir.
aquí está mi yarn.lock si está interesado en ir más allá: https://pastebin.com/msARLta1
pero no se que puede ser diferente

Veo errores similares "ExpressionChanged ..." después de cambiar de 4.1.2 a 4.2.3 .

En mi caso, tengo dos componentes que interactúan a través de un servicio. El servicio se proporciona a través de mi módulo principal, y ComponentA se crea antes de ComponentB .

En 4.1.2 , la siguiente sintaxis funcionó bien sin errores:

Plantilla ComponentA:

<ul *ngIf="myService.showList">
...

Clase ComponentB:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

En 4.2.3 , tengo que modificar la plantilla de ComponentA de la siguiente manera para evitar el error "ExpressionChanged ...":

Plantilla ComponentA:

<ul [hidden]="!myService.showList">
...

Todavía estoy tratando de averiguar por qué esto acaba de convertirse en un problema y por qué funciona el cambio de *ngIf a [hidden] .

Tengo el mismo problema, y ​​ocurre principalmente cuando estoy usando una tubería asíncrona como esta:
<div>{{ (obs$ |async).someProperty }}</div>
Si lo uso así:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

Luego, el error desaparece, así que no estoy tan seguro de que sea solo porque mucha gente cometió un error que no se detectó antes: creo que se introdujo un error en 4.2 ...

El mismo problema para mí. El problema parece provenir de @ angular / enrutador

También he llegado a la conclusión de que está relacionado con el enrutador. Intenté publicar tanto ayer, pero parece que la publicación falló. Investigué más y simplifiqué el código para ver si podía rastrear el error. El siguiente código funciona perfectamente cuando la fuente final de la acción es un clic en el encabezado de un panel, pero falla con el error ExpressionChanged cuando se activa mediante enrutamiento.

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

¡Esto me ayudó a resolver el problema! Tienes que activar explícitamente el cambio en el padre.

Como @acidghost , podría resolver este problema ejecutando ChangeDetectorRef#detectChanges en AfterViewInit desde mi componente principal.

Si tiene plunkr repro, estoy dispuesto a verificarlo porque tuvimos algunas quejas similares en gitter y siempre terminó siendo un problema de usuario.

No puedo estar seguro si se introdujo un error en 4.2 o si el error fue más bien la falta de error en <4.2 y se resolvió en 4.2.

Error: ExpressionChangedAfterItHasBeenCheckedError
solución:
componentRef.changeDetectorRef.detectChanges ();

A continuación se muestra una pieza de mi código (componentes generados dinámicamente):
componentRef = viewContainerRef.createComponent (componentFactory); // componente se creó
(componentRef.instance) .data = input_data; // cambiar manualmente los datos de los componentes aquí
componentRef.changeDetectorRef.detectChanges (); // resultará en la detección de cambios

Para componentes simples, solo busque en Google cómo acceder a "componentRef" y luego ejecutar
componentRef.changeDetectorRef.detectChanges ();
después de configurar / cambiar datos / modelo del componente.

Solución 2:
Nuevamente recibí este error al abrir un cuadro de diálogo "this.dialog.open (DialogComponent)".
así que poner este código de diálogo abierto dentro de una función y luego aplicar setTimeout () en esa función resolvió el problema.
ex:
openDialog () {
let dialogRef = this.dialog.open (DialogComponent);
}
ngOnInit (): void {
setTimeout (() => this.openDialog (), 0);
}
aunque parece hack .. funciona !!!

Desde 4.2 tengo el mismo problema y ninguna solución o solución alternativa funcionó para mí :( Creo un componente dinámicamente con una fábrica de componentes en un método ngAfterViewInit. Este componente usa una directiva que tiene un campo @Input() . Ato esto campo a una cadena estática Parece que el campo @Input() de la directiva no está definido hasta que la vista del componente fue construida y verificada y solo entonces se inicializó con la cadena estática.

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd?p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

test.directive.ts

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

dynamic.component.ts

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: La expresión ha cambiado después de que se verificó. Valor anterior: 'indefinido'. Valor actual: 'prueba'. Parece que la vista se ha creado después de que su padre y sus hijos hayan sido revisados ​​incorrectamente. ¿Se ha creado en un gancho de detección de cambios?

Estoy un poco confundido leyendo este hilo. ¿Es este un error conocido que se resolverá en una próxima versión? ¿O es este comportamiento esperado? Si es así, ¿cuál es la solución? Gracias.

No tengo idea de si está relacionado o no, ¡pero odio este mensaje con pasión! esto SOLO se me muestra cuando la unidad prueba con el comando 'ng test' listo para usar. Supongo que podría estar confundido acerca de cómo simular con éxito un servicio simple. También me sorprende que nadie más haya visto esto en las pruebas todavía, no puedo encontrar nada relacionado con las pruebas. Estaré viendo este tema y cualquier búsqueda de ExpressionChangedAfterItHasBeenCheckedError + pruebas unitarias.

He intentado armar un plnkr y me resulta muy difícil reproducir el error; sin embargo, creo que una depuración exhaustiva me ha acercado un poco más a ver qué está mal (aunque no a decidir si es un error, o un error de mi parte).

Creé una pequeña aplicación de muestra usando versiones reducidas de los mismos reductores combinados en el mismo orden que en la aplicación real, con el routerReducer al final. Para mi frustración, el error no ocurrió. Así que establecí puntos de corte y observé el flujo de acciones a través de los reductores. Lo que encontré fue que las dos aplicaciones estaban enviando acciones en orden inverso.

Mi reductor de interfaz de usuario gestiona la visibilidad y es la fuente del cambio de estado que desencadena el error. Aquí hay una versión reducida del reductor, que muestra las acciones relevantes:


export const userInterfaceReducer = (state = INITIAL_UI(), action: any) => {
    switch (action.type) {
        case IngredientActions.LIST_INGREDIENTS:
            if (action.hideTypes) {
                return Object.assign(state, { visiblePanel: VisiblePanel.Ingredients });
            }
            return state;
        default:
            return state;
    }
}

Cuando ejecuto esto en mi pequeña aplicación de prueba, funciona: primero, la acción LIST_INGREDIENTS se activa y se devuelve el nuevo estado; luego se activa la acción @ angular-redux / router :: UPDATE_LOCATION, y se activa el caso predeterminado, devolviendo el mismo estado.

Cuando lo ejecuto en la aplicación real, el orden o las acciones se invierten. La acción @ angular-redux / router :: UPDATE_LOCATION se dispara primero, devolviendo el estado anterior por defecto. Luego, LIST_INGREDIENTS se activa, devolviendo el nuevo estado (cambiado) y se genera el error.

Estoy completamente abierto a la idea de que he hecho algo tonto y esto no es un error real, pero si es así, ¿alguien más ve lo que hice?

(Como nota al pie, verifiqué la versión 4.1.3 ... y también activa los reductores redux en el orden 'correcto', es decir, el cambio de ubicación se activa después de la acción de lista de ingredientes, que presumiblemente es la razón por la que funciona, pero la versión 4.2 no lo hace).

Sufriendo el mismo error molesto cuando un componente cambia la propiedad del servicio. Se resuelve moviendo * ngIf al componente principal. Entonces definitivamente es un error

pudo solucionar esto usando el método ngDoCheck () y llamando al método ChangeDetectorRef.detectChanges.

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

@pappacurds No hagas eso. Ha provocado ciclos infinitos de detección de cambios con eso.

podría ser el mismo error aquí https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb?p=preview

error: previous undefined after undefiend

después de actualizar el mismo error

Lo mismo para mi

Todo bien en 4.0.0, después de actualizar a 4.2.6 obtengo el mismo error.

El artículo Todo lo que necesita saber sobre el error ExpressionChangedAfterItHasBeenCheckedError explica en profundidad por qué ocurre este error y posibles soluciones.

las correcciones son obvias, pero cambiar el marco de tal manera que la actualización haga que las aplicaciones de usuario se reescriban para "admitir" una nueva actualización menor de la plataforma. JS todavía mantiene viejos errores para mantener la compatibilidad ...

Mismo error aquí

También veo este problema en ~4.2.4 . La degradación a ~4.1.3 resolvió el problema.

El problema es con los valores cambiantes de ContentChildren , incluso cuando se usa ChangeDetectorRef detectChanges() al final de AfterViewInit del padre (donde estaba haciendo los cambios). Curiosamente, este problema solo ocurre con componentes y no con directivas. Convertí una directiva que tenía trabajando en 4.2.4 para que fuera un componente con una plantilla de <ng-content></ng-content> y luego comenzó a lanzar el error ExpressionChangedAfterItHasBeenCheckedError .

Obtuve esto mientras trabajaba con el control de entrada del material 2. Intenté establecer el valor de la misma

ngAfterViewInit(): void {
this.numberFormControl.setValue(200);
}

He reunido un Plunker simple que muestra un caso en el que los errores ExpressionChangedAfterItHasBeenCheckedError solo comenzaron a aparecer comenzando con Angular 4.2.x .

He creado un plunker simple que muestra el error al cambiar un valor de ContentChild incluso cuando se usa ChangeDetectorRef detectChanges() en el ngAfterViewInit del componente principal.

Editar: También creé un plunker simple basado en mi primer ejemplo pero con la directiva principal en lugar de Componente y sin error.

@saverett Veo un mensaje de error diferente con su plunk

@JSMike Tuve exactamente el mismo caso que en su plunker (lo solucioné envolviendo el código que hace referencia a ContentChild en setTimeout , plunker )

@JSMike Gracias por el

@matkokvesic esa no es realmente una solución, solo hace que el cambio ocurra fuera del gancho del ciclo de vida. El ejemplo de código proporcionado no causa un error al usar Angular v4.1.3

@JSMike Estoy de acuerdo, es una solución temporal. El uso de elementos referenciados por ContentChildren en ngAfterViewInit funcionaba bien antes de Angular 4.2.6.

Lo arreglé usando changeDetection: ChangeDetectionStrategy.OnPush y this.changeDetector.markForCheck(); en mi código

@touqeershafi si usa this.changeDetector.markForCheck(); ¿no significa que no está usando las cosas de la manera correcta?

Esto todavía está sucediendo. Definitivamente es un error, no un comportamiento esperado y debe corregirse.

@istiti, entiendo que no es una forma adecuada, pero al menos puede deshacerse de este error temporalmente, pero en lo que respecta a mi proyecto, ya agregué el TODO con la URL del error.

Esto todavía está sucediendo. Definitivamente es un error, no un comportamiento esperado y debe corregirse.

Creo que tengo que estar de acuerdo con @rwngallego ... Antes de la actualización 4.2.x, ya había escrito una serie de componentes stlye reutilizables / ng-content que dependen de @ViewChildren , @ContentChildren y crean relaciones entre padres e hijos a través de estos canales (obtener / establecer propiedades, etc.).

Constantemente tengo que inyectar la instancia de ChangeDetectorRef en todos estos constructores (incluidas las clases secundarias que extienden estos componentes), y no parece un comportamiento intencionado o correcto.

Para los que usan un componente dinámico como @saverett (usar un enrutador es usar un componente dinámico), creo que es el problema que se indica aquí https://github.com/angular/angular/issues/15634.

Para cualquiera que haga cambios dentro de ngAfterViewInit / ngAfterViewChecked , creo que es lo esperado.

Para los demás, (todavía no he visto ninguno aquí) sigue siendo un misterio para mí.

Esto me está sucediendo en el siguiente escenario.
Tener un componente principal que utilice un componente secundario. El componente hijo necesita datos que el padre proporcionará, pero estos datos provienen de un resultado de promesa en el constructor padre. Entonces:

padre

constructor {
dataservice.getData().then((result) => {
this.source = result;
});
}

Plantilla principal

<app-child [source]="source"></app-child>

componente hijo

@Input() source:any[];

Este código arroja este error, sin embargo, agregarlo al componente principal resolvió el problema:

constructor(private cdRef: ChangeDetectorRef) {
dataservice.getData().then((result) => {
this.source = result;
 this.cdRef.detectChanges();
});
    }

Ahora bien, ¿es esta una solución recomendada o es una solución para un error y cuáles serían las implicaciones de tener tal cambio? para mí, parece que le está diciendo a Angular que detecte cambios, lo que creo que es algo que debe evitar, así que ...

@sulhome ... su problema es que la promesa en el constructor del padre está cambiando @Input en el componente hijo ... durante el ciclo de CD y su confirmación en modo dev. Ésa es la razón del error.

La línea this.cdRef.detectChanges(); agrega el segundo ciclo de CD para que la confirmación después de que sea exitosa y => no haya error.

Entonces todo funciona como se esperaba. Es importante comprender cómo funciona usar las construcciones correctas y la lógica general en su código para evitar este tipo de problemas. Puede leer sobre esto en la explicación profunda aquí: https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

@ mlc-mlapis, ¿estás diciendo que lo estoy haciendo bien agregando this.cdRef.detectChanges(); y que esto no tiene ninguna implicación?
Pensé que al escuchar ngOnChanges en el niño, debería elegir el cambio en el @Input del padre. ¿No es ese el enfoque correcto para hacerlo? Tenga en cuenta que lo hice y todavía recibía el error, pero pensé que para eso está diseñado ngOnChanges

@sulhome Sí, this.cdRef.detectChanges(); es lo que ayuda, pero también tiene la consecuencia de que ahora tiene 2 CD ... así que depende de cómo y dónde esté usando la estrategia OnPush en los componentes para eliminar la sobrecarga de un ciclo de CD más.

Si es cierto que dataservice.getData toma algunos datos usando http, entonces probablemente la solución más fácil sería tener un observable en ese servicio y suscribirlo desde el componente secundario. Entonces, cuando los datos se emitan en la transmisión, el niño puede reaccionar.

@ghetolay para el registro, hay algunos casos en los que es un error, vea por ejemplo # 18129 que abrí.

Tengo este problema cuando obtengo datos del componente secundario con @Output.

en el hogar de los componentes de los padres.

drag_second(trigger){ console.log("dragged222222"+trigger); if(trigger){ this.isactive=true; }else{ this.isactive=false; } }
y home.html
<div class="upper" [ngClass]="{'isactive':isactive}">

isactive se cambia cuando dragstart y dragend ....

el mensaje de error es
ExpressionChangedAfterItHasBeenCheckedError: la expresión ha cambiado después de que se verificó. Valor anterior: 'verdadero'. Valor actual: 'falso'.

¿O hay alguna otra forma en que pueda cambiar / agregar el atributo de clase?

El mismo problema en 4.2.xy 4.3.0 pero no 4.1.x.

El error solo ocurre en rutas que usan ng-template .

Solución: poner todo el código en ngAfterViewInit en setTimeout resolvió el problema por mí (gracias / créditos a @jingglang).

public ngAfterViewInit(): void {
    setTimeout(() => {
        //my code
    });
}

@Wernerson, echar un vistazo a la JSMike comentario sobre setTimeout. Es importante tener en cuenta que en realidad solo está cambiando el código fuera del enlace del ciclo de vida.

Mi problema fue que usé ActivatedRoute params Observable para modificar algún estado en el ngOnInit() . Parece que el Router emite el sincronizador params , por lo que la actualización ocurre durante el CD.
Mi solución simple para esto es actualmente:

  // !!! see delay(0)
  // results are consumed by async Pipe, where I also had the CD issue (null vs. undefined)
  this.results = this._activatedRoute.params.delay(0)
    .do(params => this.searchParameters = this._createSearchParameters(params))
    .switchMap(p => {
      let key = this.searchParameters.key();
      return this._searchResultCache.results.map(r => r.get(key));
    });

(Sé que este no es el código reactivo / funcional más hermoso de todos los tiempos;))
Entonces la solución fue delay(0) la emisión del parámetro por un tick .

Espero poder ayudar a otras personas que tengan problemas similares.
También se agradece cualquier inquietud sobre cómo mi solución es incorrecta o peligrosa.

¿Cuál es el estado de este problema? Todavía es evidente en Angular 4.2.6.
¿Es esto un problema en las construcciones de producción? Como este error solo ocurrirá en compilaciones de depuración.

Odio todas las soluciones alternativas con la ejecución manual de la detección de cambios, o incluso ejecutarla en un setTimeout () ...

setTimeout() es ineficiente porque provoca un nuevo ciclo de detección de cambios en toda la aplicación, mientras que detectChanges() solo verifica el componente actual AFAIK.

Mi equipo tuvo un malentendido fundamental sobre lo que es y no es ngOnInit. Los doctores dicen

ngOnInit() : Inicialice la directiva / componente después de que Angular muestre primero las propiedades vinculadas a los datos y establezca las propiedades de entrada de la directiva / componente.

Nos estábamos suscribiendo a nuestros servicios (que realizan un seguimiento del estado de la solicitud) desde ngOnInit . Nuestras suscripciones son BehaviorSubjects sincrónicos ; disparan en el momento de la suscripción. Entonces, "después de que Angular primero ... establece las propiedades de entrada de la directiva / componente", estábamos cambiando inmediatamente los valores. Aquí hay un Plunker muy simple que ilustra que eso es un problema.

El ejemplo en Tour Of Heroes finalmente usa http, que es asincrónico y, por lo tanto, no causa problemas. Creo que esta es la fuente de nuestra confusión.

Lo que estamos haciendo ahora es suscribirnos a nuestros estados síncronos desde nuestro constructor, para que los valores de nuestros componentes se establezcan inmediatamente. Otros en este hilo han sugerido iniciar una segunda detección de cambio a través de tiempos de espera, establecer valores más tarde o decirle explícitamente a angular que lo haga. Hacerlo evitará que aparezca el error, pero en mi humilde opinión probablemente sea innecesario.

El artículo que compartió @maximusk es de lectura obligada : Todo lo que necesita saber sobre el error ExpressionChangedAfterItHasBeenCheckedError .

Si nada más, este hilo debería resultar en una advertencia en profundidad de todo esto en los documentos angulares oficiales.

El artículo que compartió @maximusk es de lectura obligada: Todo lo que necesita saber sobre el error ExpressionChangedAfterItHasBeenCheckedError.

Gracias

@adamdport

¿ Funciona esto en el caso de usar @ContentChildren para modificar las propiedades secundarias del padre, ya que los hijos no están disponibles hasta que se haya ejecutado el ciclo de vida ngAfterViewInit ()?

Tenga en cuenta que una parte de este problema es un error confirmado y no un error del usuario:
https://github.com/angular/angular/issues/15634

El problema que resolvió @saverett es correcto, el uso de *ngIf estaba causando el error.

*acecho...

Esto puede suceder si pasa objetos a un cuadro de diálogo Material angular y luego, en el cuadro de diálogo, los usa directamente en lugar de hacer this.myDataInput = Object.assign({}, dialogDataInput) . Es decir, this.myDataInput = dialogDataInput; y luego hacer algo como this.myDataInput.isDefault = !this.myDataInput.isDefault; . Como se explicó anteriormente, el componente secundario (diálogo) está cambiando los valores que se exponen en la vista del componente principal.

reemplazar atributo con [atributo]

¿Alguien sabe con certeza si se trata de un error intencionado o un error?
¿No es correcto cargar datos en ngOnInit? Porque si muevo el código al constructor (cargando a través de un Observable) el error no se lanza ...

@Wernerson . ¿Tiene * ngIf en su html? tenga en cuenta que las cosas se procesarán después de que ngOnInit haya finalizado, solong, todo lo que hay dentro de * ngIf no funcionará y podría provocar este error.

Tiene que ser reelaborado. Con "ngIf" y controles personalizados, aparece este mensaje de error. Ciertamente, un tiempo de espera no es una solución.

@ j-nord, si leyeras los comentarios, sabrías que timeout no es obligatorio.

@ j-nord / @dgroh He experimentado lo mismo. He estado usando un ng-hint que usa una directiva ngIf y no puedo deshacerme de este error. ¿Consiguió implementar una solución alternativa?

@Wernerson sí, una parte de este problema es un error intencionado o un error: https://github.com/angular/angular/issues/15634

@ mawo87 funcionó para mí con hidden ya que no elimina el elemento del DOM. Para nuestro requisito, esto estuvo bien y también fue una solución limpia.

@ mawo87
Primero tiene que encontrar la variable que significa el mensaje de error. @ Equipo : Sería bueno tener el nombre de la variable en el mensaje de error. Esto no es fácil de encontrar con máscaras más grandes y Webpack. Si encontró el comando donde ocurre, el comando en la siguiente línea le ayudará:

this.cdr.detectionChanges();

Vea el ejemplo de vermilion928 del 19 de junio.

Gracias @dgroh y @ j-nord.
@ j-nord También estaba mirando el método de detección de cambios, pero es posible que no use este enfoque, ya que en el siguiente artículo se desaconsejó:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

En mi caso, estoy usando un servicio PageTitle y me estoy suscribiendo a él en el componente principal, y luego, cuando los componentes secundarios se cargan en la vista, establecen el PageTitle en el servicio y el padre obtiene el valor y lo muestra a través de la suscripción. Esto funcionó sin errores en 4.1.x, pero arroja el error ExpressionChangedAfterItHasBeenCheckedError en 4.2xy versiones posteriores.

Todo sigue funcionando como se esperaba, y los errores no se muestran en las compilaciones implementadas / de producción (debido a la falta de un segundo ciclo de detección de cambios), pero en el desarrollo hay una gran cantidad de rojo en mi consola.

Todavía no tengo muy claro cuál es la solución adecuada (incluso a corto plazo). Mis suscripciones a componentes principales están en el constructor y las actualizaciones de componentes secundarios a través del servicio están en ngOnInit.

Leí la publicación 'todo lo que necesitas saber sobre ...', que fue informativa, pero no me lleva a creer que estoy haciendo algo incorrectamente y no proporciona una solución funcional al problema.

@alignsoft si establece el título de la página en los componentes secundarios en ngOnInit, entonces creo que el error es válido ... Vea el artículo que estaba vinculado antes.

@alignsoft Estoy de acuerdo, ha habido mucha discusión en torno a este error, pero sin una instrucción clara y real sobre si es válido o no ... ¿es más una "advertencia" para el desarrollador sobre los estándares y la vinculación / manipulación de datos?

En mi caso específico, estoy tratando con componentes padre / hijo usando @ViewChild y @ContentChild , y tengo que llamar a detectChanges () una y otra vez para evitar que el error se muestre en la consola ...

@rolandoldengarm Intenté suscribirme en el constructor principal y establecer los valores en AfterViewInit en los componentes secundarios y aún así obtengo el error. ¿Es esa la forma correcta de manejar esto según el flujo y el ciclo de detección de cambios, y el error es potencialmente un error?

Por favor, sería genial tener algún tipo de respuesta oficial aquí ...

Mirando esto de nuevo. Parece mi ejemplo de https://github.com/angular/angular/issues/17572#issuecomment -315096085
debería haber estado usando AfterContentInit lugar de AfterViewInit al intentar manipular ContentChildren como parte del ciclo de vida del componente.

Aquí está el plnkr actualizado con AfterContentInit y no más errores: http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@JSMike Cambié AfterViewInit a AfterContentInit , sigue siendo el mismo error. Incluso si actualizo mis datos dentro de OnInit se produce el error. Hasta donde yo sé, esto es de acuerdo con el ciclo de vida angular correcto (¿verdad?) Para inicializar los datos durante OnInit . Junto con el hecho de que este error no ocurrió en versiones anteriores, supongo que se trata de un error. Sin embargo, me gustaría tener algún tipo de declaración del equipo de desarrollo si es así ...? :RE

@Wernerson , no debería actualizar datos dentro de ngOnInit menos que los datos estén disponibles en ese enlace del ciclo de vida. Debe mover su lógica para que esté en ngAfterContentInit si tiene que ver con contenido secundario.

@JSMike No ayudó, el error aún ocurre. Me acabo de suscribir a un cambio de estado en la aplicación después de recibir los datos solicitados (asincrónicamente) en ngAfterContentInit .

@Wernerson , ¿tienes un ejemplo de plnkr?

Muestra básica con StackBlitz
Un componente hijo que emite un evento y un componente padre que actualiza la variable local.
Obtener error en la consola js:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

Descubierto, el ngAfterContentInit solo funciona si los niños son estáticos, todavía falla al crear niños usando *ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@soldiertt solo necesitas detectChanges () https://stackblitz.com/edit/angular-qfu74y

@JSMike si hago esto, ngAfterViewInit del componente secundario no se activa: https://stackblitz.com/edit/angular-bdwmgf

@soldiertt eso parece extraño, pero parece un problema separado que debería informarse.

@JSMike Mi configuración es bastante compleja con servicios, bibliotecas de terceros, información confidencial, etc. etc. Desafortunadamente, no pude reproducir el error en un plunker: /

¡La inyección de ChangeDetectorRef funcionó para mí!

Tengo este problema en un carrusel, porque necesito establecer alguna propiedad en el componente del elemento del carrusel, propiedad que solo se conoce después de calculada en el componente del carrusel principal. Y todos los elementos vienen a través de ng-content , por lo que solo puedo trabajar con los elementos del ngAfterContentInit del componente carrusel principal. Algo como:
carrusel:

@ContentChildren(ItemComponent) carouselItems;

ngAfterContentInit() {
  // ... some computations
  this.carouselItems.forEach((item: ItemComponent, currentIndex) => {
    item.setPropertyX(someComputedValue);
    // OR:
    item.x = someComputedValue; // of course, this would work as well
  });
}

articulo:

setPropertyX(value) {
  this.x = value;
}

Además del truco setTimeout() , ¿la única otra solución sería utilizar un servicio y un sujeto para que los elementos se comuniquen con el carrusel principal? Quiero decir que sería ridículo ... Eso significaría que, si tengo 100 elementos en un carrusel, entonces tendría 100 suscripciones. Y eso se duplica si tengo 2 carruseles. ¿Seguramente esa no puede ser la forma recomendada? ¿Correcto?

La mayoría de las veces, necesitaríamos establecer algunas propiedades en nuestros componentes, propiedades que no se pueden conocer antes de AfterContentInit ... Y en muchos casos (cuando no estamos hablando de 1 o 2 componentes como eso, sino 50 o 100 como en un carrusel) con décimas o cientos de suscripción sería ridículo.

¿Hay alguna parte realmente interesante de Angular que pueda ayudarme aquí, que aún no he descubierto?

PD : También intenté inyectar ChangeDetectorRef y usar detach() tanto en el componente de carrusel como en el componente de artículo. Hubiera esperado que esto corrigiera el error ya que, bueno ... básicamente estoy diciendo "_detección de cambio de tornillo, en realidad no lo necesito en absoluto. Por favor, por favor: no detecte NINGÚN cambio _" ... ¡pero el error todavía se lanza! Entonces, ¿cuál es el punto del método detach() , si no hace lo que dice?

constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach();
}

setTimeout() no es necesario un truco , puede ser una forma muy válida de hacer cosas como, por ejemplo, cuando reacciona a la vista para actualizarla, por ejemplo: cambiar los enlaces de la plantilla durante ngAfterViewInit .

El problema es cuando lo usas como magia sin saber realmente por qué. Pero hay un escenario totalmente válido en el que usar setTimeout() es la forma correcta.

@ghetolay ¿ peligroso romper el ciclo de vida de los componentes que el sistema de detección de cambios está evaluando? He estado siguiendo la ruta ChangeDetectorRef.detectChanges (), pero eso incluso me parece un truco ... si alguna de estas soluciones es "aceptable", supongo que tendremos que vivir con eso, pero realmente rompe cualquier uso de la modificación de ViewChild (s) o ContentChild (s)

@fugwenna

¿No es peligroso romper el ciclo de vida de los componentes que el sistema de detección de cambios está evaluando?

No entiendo esa pregunta. romper el ciclo de vida?

Angular ejecutará una ronda de CD cada vez que ocurra un evento asíncrono (evento dom, xhr, setTimeout, etc.), durante ese CD actualizará la vista. ngAfterViewInit hook se ejecuta después de eso, por lo que no hay más CD planeado para ejecutarse y no se activará nuevamente hasta que ocurra otro evento asíncrono. Usar setTimeout() es la forma más sencilla de activarlo nuevamente. Sin duda, podría hacerlo usted mismo, pero creo que los beneficios son insignificantes.

@ghetolay Estoy haciendo referencia al comentario de @JSMike y algunos otros hilos que he leído:

@matkokvesic esa no es realmente una solución, solo hace que el cambio ocurra fuera del gancho del ciclo de vida. El ejemplo de código proporcionado no causa un error al usar Angular v4.1.3

Entonces, creo que usar un setTimeout () hará que el enlace del ciclo de vida se "omita" o no se evalúe como lo haría normalmente.

@ghetolay @fugwenna ¿qué pasa con el método detach() del ChangeDetectorRef ? ¿Alguna idea? ¿No debería eso desactivar la detección de cambios por completo, eliminando así también el error? ¿No entiendo por qué no funciona? ¿O el error es "independiente" de eso? Lo que significa que a Angular realmente no le importa el hecho de que hayamos deshabilitado la detección de cambios, ¿solo le importa que hayamos cambiado una propiedad después de que se haya establecido?
Si la gente todavía discute que "ExpressionChangedAfterItHasBeenChecked" es un error o no, tal vez este definitivamente sea un error ... En mi opinión, Angular ya no debería preocuparse por que cambiemos las propiedades después de que se hayan establecido, si lo hemos hecho completamente detección de cambios deshabilitada. Especialmente de la forma en que lo hice, para el componente principal y todos los demás componentes secundarios, básicamente en un árbol completo.

Estoy un poco perdido aquí, uno de los problemas que tengo es que no estoy seguro de cómo podría hacer algunas cosas muy comunes sin activar la excepción en el modo de desarrollo.

Por ejemplo, he escrito un componente de acceso al valor de control que encapsula un selector de fechas. Alimento este componente con un modelo vacío que se pasa al selector de fechas internamente, que lo inicializa y lo devuelve a través de la función registrada. Por supuesto, los niños modifican el modelo durante su inicio, ¡pero eso es lo que quiero! Quiero recuperar un modelo inicializado pero no realizar la inicialización en el padre porque es el selector de fechas quien entiende cómo hacerlo.

El problema es ¿cómo haría eso ahora? ¿Volver a llamar a setTimeout y activar la detección de cambios? ¿Usar ChangeDetectorRef? No me gusta porque realmente dificulta la lectura del código. ¿Inicializar algo en un servicio separado? Pero ese servicio solo tiene un propósito muy técnico sin ningún valor comercial y ni siquiera estoy seguro de que esto resuelva el problema.

@fugwenna Mi principal preocupación era que funcionaba en 4.1.3 y ahora no funciona. No he visto a nadie en el equipo angular intervenir todavía si deberíamos usar setTimeout en nuestras funciones de inicio y dejar que las zonas se hagan cargo, pero se siente como un anti-patrón en comparación con cómo funcionaba anteriormente.

@ kavi87 Así que esta pregunta de stackoverflow en realidad me dio la elegante solución que necesitaba. Que era hacer que el emisor de eventos en mi componente secundario fuera asíncrono :
new EventEmitter(true)

Para un tema etiquetado como una regresión con 109 comentarios, este tema es decepcionantemente silencioso con cualquier palabra oficial.

Si bien todos aquí están haciendo un buen trabajo con las técnicas de autoayuda, me encantaría saber si esto es realmente un error, o simplemente un comportamiento nuevo implementado en 4.2.x en adelante. Si es así, debería haber sido incluido como un cambio importante (y, por lo tanto, ¿no una versión menor?)

@MrCroft
No estoy seguro de que deshabilitar la detección de cambios por completo para silenciar una excepción sea un buen camino a seguir, aunque en teoría, veo cómo debería / podría tener sentido.

Estoy de acuerdo con @JSMike y @rosslavery en lo que respecta a muy poca respuesta o explicación de por qué este mensaje de error apareció en la publicación 4.2x sin ninguna documentación de cambios importantes ... sin embargo, otra parte desconcertante de esto es cómo aparentemente solo se incluye modo de desarrollo, que casi parece más una advertencia de interrupción de la detección de cambios (?)

Soy mi AppComponent tengo un div.
El único propósito de ese div es mostrar una superposición ocupada.
El estado de ocupado se activa dentro de una clase de servicio http:

app.component.html

<!-- Busy Overlay -->
<div *ngIf="busy$ | async"
     class="overlay">
    <i class="loading">
        <i><sk-wave></sk-wave></i>
    </i>
</div>

app.component.ts:

  get busy$(): Observable<boolean> {
    return this.catolService.busy$;
  }

Después de actualizar a 4.3.4, siempre obtengo un ExpressionChangedAfterItHasBeenCheckedError cuando el servicio activa el estado de ocupado.

¿Existe una solución para este escenario asincrónico simple?

@ emazv72 ... ¿esta demostración de stackblitz / plunker es la misma que en tu caso? https://stackblitz.com/edit/angular-2p2h9s?file=app%2Fapp.component.ts o https://plnkr.co/edit/RPOxSVnXvM1TqKp0peSY?p=preview

@mukunku Gracias, intentaré eso.

@ mlc-mlapis Cambié ligeramente su plunker, mi código es exactamente https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb?p=preview

pero el plunker no muestra el problema. Intentaré actualizar a la última versión 4.x para ver si se ha solucionado.

Gracias

Hola a todos, disculpas por el retraso.

Entonces, este comportamiento en realidad funciona según lo previsto, lo que me doy cuenta de que podría ser inesperado para algunas personas, así que permítanme intentar explicar la mecánica.

El plunker de @saveretts es una buena reproducción de esto para caminar (¡gracias!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN?p=preview

El tldr es que Angular siempre ha funcionado de esta manera: la detección de cambios se está ejecutando, de arriba a abajo (o padre -> hijo), en una sola pasada. Entonces, si tenemos:


@Component({ 
  selector: 'app-root',
  template: `
    <!-- initially falsey -->
    <div *ngIf="sharedService.someValue">NgIf Block</div>
    <child-comp></child-comp>
  `
})
class AppRoot {
  constructor(public sharedService:SharedService){}
}
@Component({ 
  selector: 'child-component',
  template: `child comp`
})
class ChildComponent {
  constructor(public sharedService:SharedService){}
  ngOnInit(){
    this.sharedService.someValue = true;
  }
}

La Detección de cambios se ejecutará primero para la raíz de la aplicación, verificará todos sus enlaces y luego cambiará la detección del componente secundario; nuevamente, porque hacemos esto en una sola pasada (en lugar del estilo AngularJS, siga verificando hasta que se establezca), el enlace principal (el * ngIf, en este caso) no se vuelve a marcar, por lo que ahora su aplicación está en un estado inconsistente (!)

En el modo de desarrollo, en realidad ejecutamos esa detección de cambios dos veces: la segunda vez que se ejecuta verifica que los enlaces no hayan cambiado (lo que indicaría un estado inconsistente) y arroja el error si se encuentra dicho problema. Puede verificar este comportamiento llamando a enableProdMode() en la reproducción: https://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56?p=preview. Tenga en cuenta que el error no se produce (porque no estamos ejecutando CD x2), pero la aplicación termina en un estado inconsistente (se muestra el niño, pero no el * ngIf), lo cual es algo malo.

La razón por la que esto surgió recientemente es que anteriormente, el enrutador ejecutaba una serie de comprobaciones de detección de cambios adicionales al insertar componentes; esto era ineficiente y causaba problemas con las animaciones y el enrutamiento, especialmente cuando las salidas del enrutador están anidadas en ngIfs y cosas por el estilo. correctamente un error de que el desarrollador está violando el flujo de datos unidireccional.

Yo diría que, por lo general, el código que viola este flujo unidireccional (como lo hace la reproducción anterior) es incorrecto . Sin embargo, hay casos en los que es posible que desee este comportamiento (de hecho, tenemos una configuración similar en la biblioteca de formularios angulares). Lo que esto significa es que necesita activar explícitamente otra ejecución de detección de cambios (que volvería al componente raíz y verificaría de arriba hacia abajo). Llamar a setTimeout logra esto, aunque normalmente es mejor usar una Promesa para activarlo; consulte https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv?p=preview

¡Espero que ayude!

@robwormald Este mismo error también estaba ocurriendo con otros escenarios. No estoy seguro de si esto soluciona el problema con ContentChildren . ¿Por qué una directiva puede cambiar los valores de los niños durante AfterContentInit sin errores, pero hacer lo mismo en un componente arroja errores?

Además, ¿se espera que si se llama a cdr.detectChanges() durante un proceso de inicio, las otras funciones de inicio no acaben siendo llamadas? Esto es algo descubierto en este hilo para el que creé un problema separado .

@robwormald

Creo que @JSMike tiene razón en la evaluación de que este error aún se produce durante escenarios en los que el flujo unidireccional proviene de un Padre-> Niño que usa ContentChild (s) y ViewChild (s).

¿Es aceptable considerar lo que dije antes? Este error es más una "advertencia" que indica que el desarrollador va en contra de la "mejor práctica" de flujo unidireccional, pero no causa ningún error de rotura de la aplicación, ya que la detección de cambios solo se ejecuta una vez durante el modo de producción (mundo real)?

@fugwenna No, esto es realmente un error, ya que de lo contrario deja la vista en un estado inconsistente. La muestra que Rob está analizando lo dice todo.

Además, hay esta cita de los documentos

La regla de flujo de datos unidireccional de Angular prohíbe las actualizaciones de la vista después de que se haya compuesto. Ambos ganchos se activan después de que se ha compuesto la vista del componente.

Si realmente necesita cambiar algo allí, use uno de los métodos asíncronos para hacerlo. setTimeout podría ser adecuado aquí, pero tenga en cuenta que eso viene con un retraso de 4 ms (en el momento de escribir este artículo). Otra forma es Promise.resolve().then(() => Assignment = here)

Aún mejor es dar un paso atrás y ver por qué necesita hacer un cambio tan tarde en el proceso y ver si hay una forma más limpia de resolver el problema. Si me encuentro haciendo esto, lo trato como un olor a código.

Gracias por la explicación detallada @robwormald. Todavía estoy confundido por qué un flujo de datos secundario -> padre todavía parece funcionar cuando se usa un enlace [hidden] lugar de *ngIf (consulte https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u?p = vista previa). ¿Alguna idea de por qué esto todavía funciona sin dar un ExpressionChangedAfterItHasBeenCheckedError ?

Angular usa la función checkAndUpdateView para realizar la detección de cambios.

Una breve representación de esta función podría verse así:

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    // update DOM for the current view
    updateRenderer
    // perform checkAndUpdate for all child components
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);
}

Se ejecuta de forma recursiva gracias a los métodos execEmbeddedViewsAction y execComponentViewsAction . Y también el punto clave aquí es la verificación angular de las vistas incrustadas después de que se hayan verificado las directivas y antes de actualizar DOM.

Para simplificar el ejemplo de @saverett, usaré la siguiente plantilla

<div *ngIf="!sharedService.displayList">Some text</div>
<router-outlet></router-outlet>

https://plnkr.co/edit/OdkzHjEXbEd3efYAisFM?p=preview

Ya sabemos que *ngIf es azúcar. Entonces

<ng-template [ngIf]="sharedService.displayList">
  <div>Some text</div>
</ng-template>
<router-outlet></router-outlet>

Podemos imaginar la siguiente estructura:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

¿Qué hace la directiva RouterOutlet ?

Inyecta el componente enrutado en ViewContainerRef

activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
    ...
    this.activated = this.location.createComponent(factory, this.location.length, injector);

Significa que router-outlet crea EmbeddedView así que después de que se inicialice la directiva RouterOutlet tenemos dos vistas incrustadas en nuestra vista AppComponent .

  my-app
     |
   NgIf directive ([ngIf]="sharedService.displayList")
       NgIf EmbeddedView
   RouterOutlet directive 
      BComponent EmbeddedView

Vamos a ilustrarlo en la imagen.

haschanged

Ahora volvamos a nuestra función principal.

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    ...
    // update DOM for the current view
    updateRenderer
    ...
}

Angular realizará la verificación en el siguiente orden:

NgIf directive ([ngIf]="sharedService.displayList") updateDirectives(1)
      NgIf EmbeddedView   execEmbeddedViewsAction(2)
RouterOutlet directive 
      BComponent EmbeddedView  execEmbeddedViewsAction(3)

Nuestra directiva NgIf se comprobará primero (aquí se recuerda sharedService.displayList=false ) y luego se llamará al método NgOnInit dentro de BComponent (donde estamos cambiando sharedService.displayList a true ) Y cuando angular realiza checkNoChanges generará el error Expression has changed after it was checked

Entonces reemplacemos *ngIf con [hidden] . Ahora solo tendremos una vista incrustada insertada por RouterOutlet y nuestro [hidden]="sharedService.displayList" se comprobará dentro de updateRenderer

  my-app
     |
   div{Some Text} just a node
   RouterOutlet directive 
      BComponent EmbeddedView

Angular realizará una verificación como esta:

function checkAndUpdateView(view) {
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);   BComponent.ngOnInit (1)
    ...
    // update DOM for the current view
    updateRenderer   update hidden binding (2)
    ...
}

De esta manera, el enlace de propiedad dom no causará el error porque hemos actualizado después de llamar a ngOnInit value.

Esa es la principal diferencia entre la directiva estructural *ngIf y la vinculación de propiedad dom [hidden]

¡Gracias por la descripción detallada @alexzuza! ¡Fue muy útil!

setTimeout () realmente funcionó para mí :) Gracias @jingglang :)
.subscribe (data => setTimeout (() => this.requesting = data, 0))

@robwormald tiene razón aquí. Tuve el mismo problema y fue más difícil de detectar porque estaba usando NGRX y observables. El problema que tuve fue que había contenedores de componentes en diferentes niveles en la jerarquía de rutas que se suscribían al estado de la tienda. Tan pronto como moví los componentes de mi contenedor al mismo nivel en mi módulo de funciones, el problema desapareció. Sé que esta es una especie de respuesta complicada, pero si está utilizando el patrón de contenedor / componente NGRX, espero que esto dirija su investigación en la dirección correcta

Con setTimeOut funciona, gracias @jingglang
ngAfterViewInit () {
this.innerHeight = (ventana.innerHeight);
setTimeout (() => this.printPosition (), 0);
}

@alexzuza , esto no está sucediendo solo para ngIf, esto está sucediendo (al menos para mí) también con [ngClass]

@ Ninja-Coding-Git Acabo de describir un caso específico. Sé que puede pasar con cualquier código.

Para mí, llamar a ChangeDetectorRef después de cambiar el elemento resolvió el problema:

import { Component, ChangeDetectorRef } from '@angular/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  constructor(private _cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this._cdr.detectChanges();
  }

}

Solución encontrada aquí

hola @thetylerb! ¿Podría elaborar más sobre esto? Estamos usando ngrx y no sé cómo compensar esto. Creo que usar promesas además de observables es bastante incómodo.

@ piq9117 también puede echar un vistazo a este comentario https://github.com/angular/angular/issues/18129#issuecomment -326801192 que hice sobre la interacción entre la detección de cambios y ngrx, supongo que puede ser útil

@victornoel gracias! Eso describe bastante bien nuestra situación. Tenemos estructuras de datos inmutables anidadas (immutablejs) y estamos usando tuberías asíncronas en todas partes. Mis pensamientos iniciales fueron, la tienda cambiaría el árbol de componentes cuando la tienda se haya hidratado, y el árbol de componentes simplemente compensaría este cambio y haría otro cambio Detección 😓

Tenemos estructuras de datos inmutables anidadas (immutablejs) y estamos usando tuberías asíncronas en todas partes. Mis pensamientos iniciales fueron, la tienda cambiaría el árbol de componentes cuando la tienda se haya hidratado, y el árbol de componentes simplemente compensaría este cambio y haría otro changeDetection.

En mi proyecto tenemos algún tipo de estructura de datos normalizada (en lugar de estructuras de datos anidadas), por lo que mitiga un poco este tipo de problemas, ¡hasta que no lo hace! : O

Tu única esperanza es:

  • cambiar las estructuras de datos de su tienda para que los cambios en una parte no activen actualizaciones en otras partes
  • teniendo cuidado de no desencadenar eventos de componentes secundarios que escuchan la tienda a través de una tubería asíncrona
  • activar la detección de cambios manualmente cuando activa los eventos mencionados anteriormente
  • activar esos eventos en otro momento o en un setTimeout

Estoy usando ngrx y tengo una situación como esta:

En app.component.html:
<div *ngIf='(appState | async).show'>test</div>
Y en app.component.ts
this.appState= this.store.select("app");

Por supuesto, en esta situación, aparece el error, pero cuando agrego una bandera booleana en un componente como este:
app.component.html -> <div *ngIf='show'>test</div>
app.component.ts ->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

El error se ha ido, claro que es comprensible para mí por qué, pero quiero conocer tus opiniones, ¿es una buena solución? ¿Cuáles son los pros y los contras?

No uso ngrx pero tengo el problema con una simple verificación con querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);

La solución de @bogdancar con ChangeDetectorRef.detectChanges() suprime el error, pero sería bueno no tener que importar ChangeDetectorRef arreglando lo que esté causando el error en primer lugar.

Tengo el mismo problema cuando uso rxjs en angular 4.4.4. Acabo de suscribir un objeto observable proporcionado por el servicio DI. Mi trabajo a continuación:

this.eventBroadcastService.getCustomEvent({type: 'loginOpen'}).subscribe(e => { setTimeout(() => { this.isBlur = true; }, 0); });

Utilizo un setTimeout para solucionarlo. Ahora funciona, pero es complicado. No me siento bien.

ngOnChanges (changes: SimpleChanges | any) hace el truco

El problema se solucionó simplemente después de mover la lógica de parámetros de cambio dinámico dentro del método ngDoCheck ().
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61 Creo que es una muy mala idea. Angular llama mucho a ngDoCheck y si su sourceArr tiene muchos datos, podría dañar sus perfs.

Consulte https://angular.io/guide/lifecycle-hooks#other -angular-lifecycle-hooks

Preferiría recomendar por ahora usar ngAfterViewInit con setTimeout :

setTimeout(_ => /* your code here */, 1000);

ejemplo:

  public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false, 1000);
  }

@dgroh , no necesita agregar un tiempo de espera real, solo desea que js ejecute su código en una próxima tarea asíncrona, lo que significa otra ejecución de detección de cambios.

@victornoel Creo que esto funcionaría con observables a menos que llames explícitamente detectChanges de ChangeDetectorRef . Desearía que todo esto no fuera necesario.

@dgroh debes haberme entendido mal, solo estaba abogando por escribir:

public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false);
  }

¿No es posible agregar un paso después de ngAfterViewInit como setTimeout ? (por ejemplo: ngAfterViewLoad )

@ aks4it , esto parece funcionar, pero me encantaría obtener más detalles sobre cómo funciona. ¿El código de generación de componentes dinámicos es síncrono?

La demostración oficial de plunker de angular docs tiene el mismo error. ¿Podrías arreglarlo ahí? :) Gracias

@luckylooke

  ngAfterViewInit() {
    //this.loadComponent();
    this.getAds();
  }

¿esto funciona?

No entiendo su requisito, pero realmente desea un intervalo de 3 segundos. Si es así, esto funciona, e incluso si desea llamar directamente a loadComponent, tuve éxito con:

ngAfterViewInit() {
    setTimeout(()=>{this.loadComponent()},0)
  }

@istiti Pude arreglarlo con ChangeDetectorRef solución alternativa, poniendo detectChanges() después de ... data = data

Pero dado que es más plunker de los documentos oficiales, tenía curiosidad por la solución oficial del problema. No es tiempo de espera ni ChangeDetectorRef.

Lo siento, fui demasiado breve en la primera publicación. Quería señalar la gravedad del problema, ya que incluso los documentos angulares tienen el mismo código.

Solo para su información, no estoy seguro de lo que realmente está causando aquí, pero los errores desaparecen cuando habilité el modo prod en main.ts
`enableProdMode ()

// si (entorno.producción) {

// enableProdMode ();

//} `

"@ angular / core": "^ 4.2.4"
"mecanografiado": "~ 2.3.3"

@bakthaa ... se supone un comportamiento porque la verificación de control (si los componentes @Inputs() valores son los mismos) que se llama después de que cada ciclo de CD se invoca solo en el modo de desarrollo y no en el modo de producción de la aplicación Angular. Entonces, el problema está oculto en el modo de producción, pero sigue ahí ... y probablemente puede ser una fuente de diferentes efectos secundarios no deseados o engañosos en el modo de producción.

Estoy usando Angular 5, pude solucionar este problema inyectando ChangeDetectorRef en el componente y forzándolo a detectar cambios.

    constructor(private changeDetector: ChangeDetectorRef) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }

@ Sabri-Bouchlema ... sí, es una forma ... pero aún así debería reconsiderar una vez más ... por qué un componente @Inputs se cambia durante un ciclo de CD ... y decidir si el La lógica es correcta ... y es cierto que a partir del 95% de los casos es posible cambiar el código para evitar el problema sin llamar detectChanges() adicionales.

Usando angular 5.1.2. Recibo este error al abrir un diálogo de material en ngAfterViewChecked. Como informó @ aks4it , tuve que envolver el código en un setTimeout para que funcionara.

Estoy enfrentando el mismo problema con la versión 4.01
ng-version = "4.0.1

Lo mismo aquí con angular 4.4.6

Arreglar usando this.changeDetector.detectChanges();

He escrito este plunker usando variables de plantilla y no entiendo por qué tengo ExpressionChangedAfterItHasBeenCheckedError. Si cambio el orden de mis componentes, el error desaparece:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

He intentado changeDetector.detectChanges pero no funciona

@AlexCenteno ... porque la lógica de evaluación es de arriba a abajo ... y value propiedad de comp1 se establece @Input() public value:String=null; primero y luego se reajusta usando la referencia de comp2.value ... <div comp1 [value]="comp2.value"></div> que se establece mientras tanto en Hello través de su propio @Input() ... todo dentro de un ciclo de CD ... por lo que la verificación repetida si las entradas en los componentes secundarios son las mismas que las del comienzo del ciclo de CD en modo dev, detecta el cambio en la propiedad value en comp1 .

hola @ mlc-mlapis gracias por su tiempo. ¿Entonces no hay forma de evitar el error? Supongo que es algo de lo que no debería preocuparme una vez que llame a enableProdMode.

@AlexCenteno ... sí, en el modo prod no se invoca la comprobación después del ciclo del CD ... pero eso no significa que deba estar satisfecho con eso. Debe aplicar una lógica diferente para pasar los componentes secundarios cruzados value . También existe el problema de que <div> no tiene una propiedad value . ¿Por qué utiliza el componente de atributo? Por qué no solo:

<comp1 [value]="comp2.value"></comp1>
<comp2 #comp2 value="Hello"></comp2>

porque entonces usar @Output() en <comp2> estaría bien y le permitiría actualizar [value] en comp1 sin ningún error.

Usando [email protected] con angular@5 standart store.select throw warning ExpressionChangedAfterItHasBeenCheckedError en modo dev y funciona incorrectamente en modo prod. El envío y la selección se realizan en diferentes niveles / contenedores.

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': (isActiveSidenavMenu$ | async)
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  ...
}

Este enfoque elimina el mensaje de advertencia, pero parece ser más complicado:

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': isActiveSidenavMenu
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  isActiveSidenavMenu;

  ngOnInit() {
    this.isActiveSidenavMenu$.subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
  }
}

¿Hay alguna idea de cómo solucionar este problema de forma más correcta?

@rimlin, ¿has intentado suscribirte directamente a la tienda?

ngOnInit() {
  this.store.select(fromHome.isActiveSidenavMenu)
    .subsbrice(res => {
      this.isActiveSidenavMenu = res;
    });
}

Creo que tuve un problema similar. y al evitar async en la plantilla resolvió el problema.

@ piq9117 gracias por el consejo, probé su solución, parece que es más claro con menos código, pero aquí también es necesario llamar a this.cd.detectChanges(); , de lo contrario, la vista no se actualizará.

ngOnInit() {
    this.store.select(fromHome.isActiveSidenavMenu).subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
}

Estoy usando Angular 5.2, hice algo así. Funciona para mi

constructor(private changeDetector: ChangeDetectorRef) {}

ngAfterViewInit(){
  this.changeDetector.detectChanges();
}

Usando Angular 5.2, arreglado usando detectChanges ()

Sí, también solucionamos esto usando la ruta detectChanges () (Angular 5.1). Estoy con muchos otros aquí, aunque piensan que esto sigue siendo solo una solución y no debería tratarse como la respuesta a largo plazo. Debería haber otro enlace de ciclo de vida para poder usar.

Activar un ciclo de detección de cambios completamente nuevo para permitir cambios como este parece subóptimo.

Esto parece seguir y seguir a pesar de que todas las respuestas y consejos ya se han publicado anteriormente.

  1. La forma correcta de solucionar esto es rediseñar / reestructurar sus componentes y el manejo de datos para que funcionen con una sola pasada de detección de cambios de arriba hacia abajo. Esto significa que ningún componente secundario puede cambiar un valor que se utiliza en la plantilla de un componente principal. Vuelva a evaluar lo que ha creado y verifique que los datos fluyan solo en una dirección, hacia abajo en el árbol de componentes. Esto puede ser tan simple como mover su código de ngAfterViewInit a ngOnInit en algunos casos.
  2. this.changeDetectorRef.detectChanges(); es la forma oficial y reconocida de manejar esto en menos del 5% de los casos donde el primer paso es problemático.

Otras notas:

  1. Tenga mucho cuidado al hacer algo en ngAfterViewInit ya que cambiar los valores que se usan en el DOM puede causar este error.
  2. Tenga mucho cuidado al utilizar cualquiera de los ganchos del ciclo Checked vida Check o Checked , ya que se pueden llamar en cada ciclo de detección de cambios.
  3. setTimeout puede solucionar esto, pero se prefieren las dos soluciones anteriores.
  4. Ignorar este error en el modo de desarrollo y estar contento de que el modo de producción "corrija" el error no es válido como se explicó anteriormente. Estas comprobaciones no se realizan en el modo de producción, pero el código del modo de producción aún deja cambios sin ser detectados y hace que la vista y los datos no estén sincronizados. Esto puede ser inofensivo en algunos casos, pero puede provocar daños en los datos / vistas y peores problemas si se ignora.
  5. El flujo de datos de una forma realmente no ha cambiado mucho en Angular, por lo que publicar que ha llegado a esto en la versión X de Angular no es demasiado útil.
  6. Agregar más enlaces o estados del ciclo de vida no es una solución para esto. Los desarrolladores simplemente agregarían código que viola el flujo de datos de una forma en esos ganchos y desencadenarán este mismo error.

Finalmente, recuerde que este error no indica un problema con Angular. Este error está aquí para hacerle saber que ha hecho algo mal en su código y que está violando el sistema de flujo de datos unidireccional que hace que Angular sea rápido y eficiente.

Las publicaciones anteriores y muchas publicaciones de blogs profundizan en esto y vale la pena leerlas, ya que debe comprender este modelo de flujo de datos unidireccional y cómo construir componentes y pasar datos dentro de él.

@Splaktar ¿Sería mejor bloquear este problema (y eliminarme) para que el comentario anterior sea sobresaliente? El hilo ya se vuelve demasiado largo y la mayoría de ellos simplemente se repiten. 😃

@Splaktar Usted hace este punto "Esto significa que ningún componente hijo puede cambiar un valor que se utiliza en la plantilla de un componente padre".

Suponga que tiene un componente contenedor que incluye navegación, títulos de página, etc., y carga una colección de subcomponentes, cada uno de los cuales puede cargar sus propios subcomponentes. Los componentes cargados en el contenedor cambiarán según los datos y los subcomponentes que carguen cambiarán según los datos. Cada uno de estos subcomponentes debe poder comunicarse con el contenedor principal información sobre lo que son para que el contenedor principal pueda mostrar el Nav de nivel superior apropiado al usuario tal vez (ruta de navegación tal vez, o algo similar), también como información que se puede utilizar en títulos para identificar a nivel de contenedor lo que se carga a la vista.

En esto, que es una arquitectura extremadamente común en mi experiencia, haría uso de un servicio de estado común en el que los subcomponentes se registrarían y el componente principal se suscribe para que los niños puedan educar al padre y al padre puede mostrar la información apropiada.

Esto no sigue un enfoque de abajo hacia arriba, ya que el padre obtendría todos los datos relevantes del servicio y enviaría cualquier información requerida por los componentes secundarios a través de la comunicación unidireccional.

Esto parece ir en contra de lo que está sugiriendo anteriormente, y para mí es la fuente del problema. Cuando el componente secundario se registra en el servicio, el componente principal genera el error.

¿Es esto incorrecto?

Y nunca olvides que hay un error aprobado:
https://github.com/angular/angular/issues/15634

@alignsoft ... ¿tiene una reproducción simple de su caso y el problema como Stackblitz? Porque parece que algo más debería afectar el caso que solo la lógica que describiste.

@trotyl las soluciones de @Splaktar no están relacionadas con mi simple problema aquí https://github.com/angular/angular/issues/17572#issuecomment -311589290 o?

@ Martin-Wegner ... en su caso es suficiente con cambiar ngAfterViewInit a ngOnInit hook.

@alignsoft , como dijo @ mlc-mlapis, un ejemplo de Stackblitz sería muy útil para demostrar la arquitectura adecuada para este caso. Esto es muy posible utilizando el conjunto correcto de servicios, observables, enlaces de ciclo de vida, etc. Este caso específico también sería un buen tema para StackOverflow.

@Splaktar Una vez que tenga mi fecha límite actual de mi escritorio, crearé un proyecto que demuestra un caso simple.

@ mlc-mlapis wow gracias, funciona :)

@alignsoft , hoy estaba creando una aplicación que se comporta exactamente como lo ha descrito. @ mlc-mlapis, puede encontrar el ejemplo simplificado en ExpressionChangedAfterItHasBeenCheckedError .

En el ejemplo, admin.component actúa como un componente principal a cargo de 3 cosas:

  • mostrando una navegación izquierda
  • un ticker superior que los subcomponentes pueden usar para mostrar su propio mensaje de ticker
  • una sección de contenido principal que muestra datos específicos de subcomponentes a través de <router-outlet></router-outlet> .

Componente principal

<div class="ticker-container" *ngIf="tickedMessage !== ''">
  <h1>{{tickedMessage}}</h1>
  <button type="button" (click)="tickedMessage = ''">
    close
  </button>
</div>

<div class="side-nav">
  <h2>side nav</h2>
  <ul>
    <li><a routerLink="/">dashboard</a></li>
    <li><a routerLink="/apps/thirdpartycms">CMS System</a></li>
  </ul>
</div>
<div class="main-content-container">
  <router-outlet></router-outlet>
</div>
export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Al investigar cómo comunicar los datos al componente principal cuando se usa <router-outlet></router-outlet> , leí que si uso @Output para enviar datos al componente principal, la directiva de salida del enrutador terminará manejando el evento. Dado que es un evento personalizado, la directiva de salida del enrutador no sabrá cómo manejarlo. Así que me salté este método. Una vez que leí sobre los servicios compartidos en angular.io , escribí el mío:

Servicio compartido

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class AdminService {

  private _tickedMessageAnnounced = new Subject<string>();

  tickedMessageAnnounced$ = this._tickedMessageAnnounced.asObservable();

  announceTickedMessage(tickedMessage: string) {
    this._tickedMessageAnnounced.next(tickedMessage);
  }
}

Una vez que uno de los componentes secundarios se llama this.adminService.announceTickedMessage(this._tickedMessage); :

Componente hijo

import { Observable } from 'rxjs/Observable';
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { AdminService } from '../../../core/admin/admin.service';

@Component({
  selector: 'cms',
  templateUrl: './thirdparty-cms.component.html',
  styleUrls: ['./thirdparty-cms.component.scss'],
})
export class ThirdPartyCmsComponent implements OnInit {
  private _tickedMessage: string;
  constructor(private adminService: AdminService) { }

  ngOnInit(): void {
    this._tickedMessage = 'Please do not create an entry for a page URL that does not exist in the CMS system';

    this.adminService.announceTickedMessage(this._tickedMessage);
  }
}

el componente padre lanzó una excepción ExpressionChangedAfterItHasBeenCheckedError porque intentó actualizar una de sus propiedades públicas con el valor del mensaje marcado enviado por el componente hijo:

Componente principal (lanza una excepción)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Lo que solucionó el ExpressionChangedAfterItHasBeenCheckedError fue ejecutar la llamada de suscripción de forma asincrónica y esperar el mensaje marcado:

Componente principal (con async en espera)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      This clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe(async (tickedMessage: string) => {
      this.tickedMessage = await tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Viniendo del mundo WPF, parece que tenemos que decirle al hilo de la interfaz de usuario que espere la devolución de llamada de suscripción observable, ya que los observables se ejecutan de forma asincrónica.

Recién estoy comenzando con Angular, así que tenga algo de compasión hacia mi habilidad Angularz: P

@ktabarez Gracias, esto me ayudó mucho!

@ktabarez ¡Gracias! ¡Eso parece funcionar muy bien! Estaba envolviendo la suscripción en el componente principal en un tiempo de espera, que también funcionaba, pero se sentía sucio.

Veo que al interrogar el estado de redux se garantiza que se entregará, pero en el inicio como este cuando se coloca en ngOnInit en lugar de los constructores

@ktabarez ¡ Muchas gracias!

Su solución async ... await in subscribe funciona.

@ktabarez Tu solución básicamente hace lo mismo que un setTimeout ({}, 0) pero se siente más elegante. ¡Me gusta mucho! También me hizo darme cuenta de que se puede esperar cualquier cosa (casi como C # await Task.FromResult (tickedMessage)), ¡así que felicitaciones por eso!

Entonces, pregunta: ¿por qué deberíamos tener que hacer un async / await en un cálculo de una propiedad en un observable que está obteniendo datos del back-end que podrían estar cambiando con frecuencia? ¿Qué pasa con ngClass que hace que esto sea necesario? Obtengo esto mientras uso un {observable | async} tubería. Entonces, async / await no es la mejor aplicación para mí. (o la tubería asíncrona no es el mejor enfoque)

Tengo este problema en el que mi componente principal tiene varios componentes secundarios. Uno de los componentes secundarios tiene una entrada donde necesita datos de la propiedad del componente principal y la propiedad obtiene sus datos de un Observable / Promise (probé ambos).

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

Encontré todas las soluciones encontradas en el hilo. Pero nada funciona en mi caso. Afortunadamente, como algunos insinuaron, este es un problema debido a la manipulación de un DOM, como poner una expresión en _ (ngIf) _ como ejemplo.

Entonces, para evitar esto, intento experimentar con ng-template , ng-container y ngTemplateOutlet .

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

Pude resolver esto sin usar ChangeDetectorRef o setTimeout()

Para mí, este error ocurrió al mostrar / ocultar el campo de acuerdo con la condición, pude resolverlo después de aplicar los valores de la configuración de campos:

this.form.get ('field').updateValueAndValidity();

He estado tratando de hacer esto con un cuadro de diálogo mat que se abre en init, y literalmente he probado todas las soluciones que he visto en este hilo (establecer tiempo de espera, resolución de promesa, tubería observable, después de iniciar la vista, después de iniciar el contenido) y combinaciones en los mismos. Todavía no funciona. Luego vi la publicación de @Splaktar sobre cómo esto viola el flujo de datos unidireccional, así que literalmente hice una bandera en ngrx diciendo que abriera el cuadro de diálogo y me suscribí a eso, que es falso inicialmente y todavía recibo este error. ¿Qué diablos hago? ¿No hay literalmente forma de abrir un diálogo modal sin adjuntarlo a algún evento completamente separado?

En el init:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

Funciones auxiliares:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

Usando angular 6.0.0-rc.1

Estoy usando angular con ngrx y obtuve este error después de enviar un formulario.
fijado por ajuste
ChangeDetectionStrategy.OnPush
en componente de formulario

todavía tengo el problema. Probé todo tipo de soluciones. setTimeout funciona la primera vez, pero la activación posterior de dialog.open resultado el mismo error. ¿Algún estado sobre esto?

Estoy enfrentando el mismo problema. Intenté todo, pero parece que este problema surge en el flujo de datos unidireccional.
Aquí estoy intentando actualizar los woids seleccionados. donde el problema se resuelve pero surge el mismo error.

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

setSelectedWoidsCount privado () {
si (this.isSelectAllWoids) {
this.selectedWoids = this.allWoids;
}
}

Porque rompes la canalización de renderizado de Angular ...

Veo errores similares "ExpressionChanged ..." después de cambiar de 4.1.2 a 4.2.3 .

En mi caso, tengo dos componentes que interactúan a través de un servicio. El servicio se proporciona a través de mi módulo principal, y ComponentA se crea antes de ComponentB .

En 4.1.2 , la siguiente sintaxis funcionó bien sin errores:

_ComponentA Plantilla: _

<ul *ngIf="myService.showList">
...

_ComponentB Clase: _

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

En 4.2.3 , tengo que modificar la plantilla de ComponentA de la siguiente manera para evitar el error "ExpressionChanged ...":

_ComponentA Plantilla: _

<ul [hidden]="!myService.showList">
...

Todavía estoy tratando de averiguar por qué esto acaba de convertirse en un problema y por qué funciona el cambio de *ngIf a [hidden] .

¿Descubrió cómo funcionaba al cambiar de ngIf a [hidden]?

¿Existe una solución para este problema en Angular 6 todavía?

@alokkarma ... demasiado viejo ... deberías

El hecho de que el mismo caso funcionara en la versión 4.1.2 está relacionado con algunos errores que se eliminaron en versiones posteriores, incluida la 4.2.3.

Estoy usando Angular 6. Estoy usando un MessageService para comunicarme entre mi componente de autenticación y el componente de mi aplicación para mostrar diferentes contenidos de la barra de navegación si el usuario está conectado (o no)

¡Funciona bien aparte de este error!
¡Probé otras soluciones como EventEmitter pero no funciona! o me sale el mismo error.

@dotNetAthlete ... muestra una demostración de reproducción simple en Stackblitz. Hablar de ello sin el código real es difícil.

@dotNetAthlete Estoy usando el mismo mecanismo básico en el que tengo un servicio de estado que contiene un título de página como asunto de comportamiento (como Observable) en el componente contenedor principal, y componentes secundarios cargados en él que establecen el título de página apropiado en el servicio de estado para que el contenedor principal lo observe y muestre.

Siempre había un error de 'Expresión cambiada' cuando el contenedor inicializaba el valor y luego el componente secundario lo actualizaba inmediatamente, así que hago esto ahora en el contenedor primario / contenedor:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

Donde el servicio estatal tiene esto:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

Esto elimina el problema. Esta solución se basa en algunos comentarios útiles de otras personas en este hilo.

@alignsoft - hermosa solución - problema solucionado en la aplicación Angular 6.

Estoy viendo esto en la página recargar en angular 7 ahora ...
Estoy estableciendo un valor de componentes secundarios en los padres ngAfterViewInit
Tuve que hacer esto en el componente secundario

  public activate() {
    setTimeout(() => this.active = true);
  }

Encontré la solución en este hilo de problemas https://github.com/angular/angular/issues/10762
Después de usar el enlace del ciclo de vida AfterContentInit , el error desaparece.

@alignsoft , solo agregando a su cambio, ya que estamos usando un BehavioralSubject en lugar de un Subject,

Lo refiné de la siguiente manera:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

Pero estoy de acuerdo con @daddyschmack , me gustaría obtener cualquier recurso que apunte a información con respecto a cualquiera de las correcciones mencionadas anteriormente ( cdr.detectionChanges() o async await o aftercontentchecked ), por qué [hidden] atributo *ngIf

@acidghost Gracias por ese enlace, resolvió mi problema. De hecho, había renunciado a intentar solucionarlo en un área de mi aplicación, pero cuando comenzó a suceder nuevamente en una nueva área ...

Este problema se ha bloqueado automáticamente debido a la inactividad.
Por favor, presente un nuevo problema si se encuentra con un problema similar o relacionado.

Obtenga más información sobre nuestra política de bloqueo automático de conversaciones .

_Esta acción ha sido realizada automáticamente por un bot._

¿Fue útil esta página
0 / 5 - 0 calificaciones