web-dev-qa-db-fra.com

L'expression ___ a changé après avoir été vérifiée

Pourquoi le composant dans cette simple plunk

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

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

lancement:

EXCEPTION: l'expression "Je suis {{message}}" dans App @ 0: 5 a été modifiée après vérification. Valeur précédente: 'Je suis en train de charger :('. 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 liaison simple quand ma vue est lancée? 

183
drewmoore

Comme indiqué par drewmoore, la solution appropriée dans ce cas consiste à déclencher manuellement la détection des modifications pour le composant actuel. Cette opération est effectuée à l'aide de la méthode detectChanges() de l'objet ChangeDetectorRef (importé de angular2/core) ou de sa méthode markForCheck(), qui met également à jour les composants parents. Pertinent exemple :

import { Component, ChangeDetectorRef } from 'angular2/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();
  }

}

Voici également des Plunkers qui démontrent les approches ngOnInit , setTimeout et enableProdMode au cas où.

195
Tycho

J'ai résolu ce problème en ajoutant ChangeDetectionStrategy à partir d'un noyau angulaire.

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

Ne pouvez-vous pas utiliser ngOnInit parce que vous venez de changer la variable membre message?

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

Un correctif modifié consiste à appeler updateMessage() dans la prochaine boucle d’événement avec, par exemple. setTimeout.

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

ngAfterViewChecked () a travaillé pour moi:

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

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}
21
Jaswanth Kumar

L'article Tout ce que vous devez savoir sur l'erreur ExpressionChangedAfterItHasBeenCheckedError explique le comportement de manière très détaillée.

Le problème avec votre configuration est que le hook ngAfterViewInit lifecycle est exécuté après que les mises à jour DOM traitées par la détection de modification ont été traitées. Et vous changez effectivement la propriété utilisée dans le modèle dans 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 de changement et Angular de par sa conception n'exécute qu'un seul cycle de résumé.

Vous avez essentiellement deux solutions pour y remédier:

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

  • effectuez la mise à jour de la propriété dans un crochet avant la mise à jour DOM - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

18

Il vous suffit de mettre à jour votre message dans le crochet de cycle de vie approprié, dans ce cas, il s'agit de ngAfterContentChecked au lieu de ngAfterViewInit, car dans ngAfterViewInit, une vérification du message variable a été lancé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: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  ngAfterContentChecked() {
     this.message = 'all done loading :)'
  }      
}

voir la démo de travail sur Plunker.

6
RedPelle

Vous pouvez également passer votre appel pour mettre à jour Message () dans la méthode ngOnInit (), au moins cela fonctionne pour moi

ngOnInit() {
    this.updateMessage();
}

En RC1 cela ne déclenche pas l'exception

4
Tobias Gassmann

Pour cela, j'ai essayé les réponses ci-dessus, beaucoup ne fonctionnent pas dans la dernière version de Angular (version 6 ou ultérieure).

J'utilise le contrôle matériel qui a nécessité des modifications après la première liaison.

    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 un problème spécifique.

4
MarmiK

Cela génère une erreur car votre code est mis à jour lorsque ngAfterViewInit () est appelé. Cela signifie que votre valeur initiale a changé lorsque ngAfterViewInit a lieu. Si vous appelez cela dans ngAfterContentInit () , aucune erreur ne sera renvoyée.

ngAfterContentInit() {
    this.updateMessage();
}

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

Une chose à noter! Si vous ajoutez changeDetection: ChangeDetectionStrategy.OnPush sur le composant ne fonctionne pas, et que c'est un composant enfant (composant bête), essayez de l'ajouter également au parent. 

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

0
TKDev

Vous pouvez également créer une minuterie à l'aide de la fonction rxjs Observable.timer, puis mettre à jour le message dans votre abonnement:

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