Expression ___ a changé après vérification

Pourquoi le composant est-il dans ce simple plunk

@Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message:ssortingng = 'loading :('; ngAfterViewInit() { this.updateMessage(); } updateMessage(){ this.message = 'all done loading :)' } }

lancement:

EXCEPTION: L’expression “Je suis {{message}} dans l’application @ 0: 5” a été modifiée après vérification. Valeur précédente: “Je charge :(“. Valeur actuelle: “J’ai tout fini de charger :)” dans [Je suis {{message}} dans App @ 0: 5]

quand tout ce que je fais est de mettre à jour une simple liaison lorsque ma vue est lancée?

Comme indiqué par drewmoore, la solution appropriée dans ce cas consiste à déclencher manuellement la détection de modifications pour le composant actuel. Cela se fait à l’aide de la méthode detectChanges() de l’object ChangeDetectorRef (importé d’ angular2/core ) ou de sa méthode markForCheck() , qui permet également de mettre à jour les composants parents. Exemple pertinent:

 import { Component, ChangeDetectorRef } from 'angular2/core' @Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message: ssortingng = 'loading :('; constructor(private cdr: ChangeDetectorRef) {} ngAfterViewInit() { this.message = 'all done loading :)' this.cdr.detectChanges(); } }

Voici également les plunkers illustrant les approches ngOnInit , setTimeout et enableProdMode au cas où.

Tout d’abord, notez que cette exception ne sera levée que lorsque vous exécuterez votre application en mode dev (ce qui est le cas par défaut à partir de beta-0): si vous appelez enableProdMode() lors du démarrage de l’application, elle ne sera pas jeté ( voir plunk mis à jour ).

Deuxièmement, ne faites pas cela parce que cette exception est lancée pour une bonne raison: en bref, en mode dev, chaque tour de détection de changement est immédiatement suivi par un second tour qui vérifie qu’aucune liaison n’a été modifiée depuis la fin du premier, comme cela indiquerait que les changements sont causés par la détection de changement elle-même.

Dans votre plunk, la liaison {{message}} est modifiée par votre appel à setMessage() , ce qui se produit dans le hook ngAfterViewInit , qui intervient lors du virage initial de détection des modifications. Cela en soi n’est pas problématique cependant – le problème est que setMessage() modifie la liaison mais ne déclenche pas un nouveau cycle de détection des modifications, ce qui signifie que cette modification ne sera détectée que setMessage() prochain cycle de détection des changements sera déclenché ailleurs. .

Le point à retenir: tout ce qui modifie une liaison doit déclencher une détection de changement lorsque c’est le cas.

Mise à jour en réponse à toutes les demandes d’exemples de la façon de procéder : La solution de @ Tycho fonctionne, tout comme les trois méthodes de la réponse @MarkRajcok. Mais franchement, ils se sentent tous laids et ont tort pour moi, comme le genre de hacks auquel nous nous sums habitués à nous appuyer sur ng1.

Bien sûr, il y a des circonstances occasionnelles où ces piratages sont appropriés, mais si vous les utilisez sur une base très occasionnelle, c’est un signe que vous vous battez contre le cadre plutôt que d’adopter pleinement sa nature réactive.

À mon humble avis, une approche plus idiomatique, «Angular2», de l’approche est quelque chose du genre: ( plunk )

 @Component({ selector: 'my-app', template: `
I'm {{message | async}}
` }) export class App { message:Subject = new BehaviorSubject('loading :('); ngAfterViewInit() { this.message.next('all done loading :)') } }

J’ai corrigé cela en ajoutant ChangeDetectionStrategy à partir du kernel angular.

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

Ne pouvez-vous pas utiliser ngOnInit parce que vous modifiez simplement le message variable membre?

Si vous souhaitez accéder à une référence à un composant enfant @ViewChild(ChildComponent) , vous devez en effet l’attendre avec ngAfterViewInit .

Un correctif est d’appeler le updateMessage() dans la prochaine boucle d’événement avec par exemple setTimeout.

 ngAfterViewInit() { setTimeout(() => { this.updateMessage(); }, 1); } 

L’article Tout ce que vous devez savoir sur l’erreur ExpressionChangedAfterItHasBeenCheckedError explique le comportement en détails.

Le problème avec votre configuration est que le ngAfterViewInit cycle de vie ngAfterViewInit est exécuté après la détection des modifications. Les mises à jour du DOM ont été traitées. Et vous modifiez effectivement la propriété utilisée dans le modèle de ce hook, ce qui signifie que DOM doit être restitué:

  ngAfterViewInit() { this.message = 'all done loading :)'; // needs to be rendered the DOM } 

et cela nécessitera un autre cycle de détection des changements et Angular par conception ne fait fonctionner qu’un seul cycle de digestion.

Vous avez essentiellement deux alternatives pour résoudre ce problème:

  • mettre à jour la propriété de manière asynchrone en utilisant setTimeout , Promise.then ou une observable asynchrone référencée dans le modèle

  • effectuer la mise à jour de la propriété dans un hook avant la mise à jour du DOM – ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

ngAfterViewChecked () a fonctionné pour moi:

 import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef constructor(private cdr: ChangeDetectorRef) { } ngAfterViewChecked(){ //your code to update the model this.cdr.detectChanges(); } 

Il vous suffit de mettre à jour votre message dans le crochet du cycle de vie ngAfterContentChecked . Dans ce cas, ngAfterContentChecked au lieu de ngAfterViewInit , car dans ngAfterViewInit, une vérification du message de variable a été démarrée mais n’est pas encore terminée.

voir: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview

donc le code sera juste:

 import { Component } from 'angular2/core' @Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message: ssortingng = 'loading :('; ngAfterContentChecked() { this.message = 'all done loading :)' } }

voir la démo de travail sur Plunker.

Vous pouvez également mettre votre appel à updateMessage () dans la méthode ngOnInt (), au moins cela fonctionne pour moi

 ngOnInit() { this.updateMessage(); } 

Dans RC1, cela ne déclenche pas l’exception

Il génère une erreur car votre code est mis à jour lorsque ngAfterViewInit () est appelée. Cela signifie que votre valeur initiale a été modifiée lorsque ngAfterViewInit a eu lieu. Si vous appelez cela dans ngAfterContentInit (), cela ne provoquera pas d’erreur.

 ngAfterContentInit() { this.updateMessage(); } 

Vous pouvez également créer un minuteur à l’aide de rxjs Observable.timer, puis mettre à jour le message dans votre abonnement =>

Observable.timer (1) .subscribe (() => this.updateMessage ());

J’ai déjà essayé cette solution: (ça a marché!)

  export class BComponent { name = 'I am B component'; @Input() text; constructor(private parent: AppComponent) {} ngOnInit() { setTimeout(() => { this.parent.text = 'updated text'; }); } ngAfterViewInit() { setTimeout(() => { this.parent.name = 'updated name'; }); } } 

Vous pouvez lire plus de détails ici: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithaeckcherrederror-error-e3fd9ce7dbb4

Je n’ai pas pu commenter le post de @Biranchi car je n’ai pas assez de réputation, mais cela a résolu le problème pour moi.

Une chose à noter! Si vous ajoutez changeDetection: ChangeDetectionStrategy.OnPush sur le composant n’a pas fonctionné et que c’est un composant enfant (composant dumb), essayez de l’append également au parent.

Cela a corrigé le bug, mais je me demande quels sont les effets secondaires de ceci.

Pour cela, j’ai essayé ci-dessus réponses ne fonctionne pas beaucoup dans la dernière version de Angular (6 ou plus tard)

J’utilise le contrôle de matériel qui nécessitait des modifications après la première liaison effectuée.

  export class AbcClass implements OnInit, AfterContentChecked{ constructor(private ref: ChangeDetectorRef) {} ngOnInit(){ // your tasks } ngAfterViewInit() { this.ref.detectChanges(); } } 

En ajoutant ma réponse, cela aide certains à résoudre des problèmes spécifiques.