Pourquoi le composant dans ce 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 l'application @ 0: 5» a changé après avoir été vérifiée. Valeur précédente: «Je charge :(». Valeur actuelle: «J'ai fini de charger :)» dans [Je suis {{message}} dans App @ 0: 5]
quand tout ce que je fais est de mettre à jour une liaison simple lorsque ma vue est lancée?
typescript
angular
a attiré moore
la source
la source
ExpressionChangedAfterItHasBeenCheckedError
erreur explique le comportement en détail.ChangeDetectionStrategy
lorsque vous utilisezdetectChanges()
stackoverflow.com/questions/39787038/…Réponses:
Comme indiqué par drewmoore, la solution appropriée dans ce cas consiste à déclencher manuellement la détection des modifications pour le composant actuel. Cela se fait à l'aide de la
detectChanges()
méthode de l'ChangeDetectorRef
objet (importé deangular2/core
) ou de samarkForCheck()
méthode, qui met également à jour les composants parents. Exemple pertinent :Voici également Plunkers démontrant la approches ngOnInit , setTimeout et enableProdMode au cas où.
la source
cdr : any
ou quelque chose comme ça sous lamessage
déclaration. Juste inquiet d'avoir raté quelque chose?Tout d'abord, notez que cette exception ne sera levée que lorsque vous exécutez votre application en mode dev (ce qui est le cas par défaut à partir de la beta-0): si vous appelez
enableProdMode()
lors du démarrage de l'application, elle ne sera pas levée ( voir plunk mis à jour ).Deuxièmement, ne faites pas cela car cette exception est levée pour une bonne raison: en bref, en mode dev, chaque cycle de détection de changement est suivi immédiatement par un deuxième cycle qui vérifie qu'aucune liaison n'a changé depuis la fin du premier, car cela indiquerait que les changements sont causés par la détection des changements elle-même.
Dans votre plunk, la liaison
{{message}}
est modifiée par votre appel àsetMessage()
, qui se produit dans lengAfterViewInit
crochet, qui se produit dans le cadre du tour de détection de changement initial. Cela en soi n'est pas problématique cependant - le problème est quesetMessage()
change la liaison mais ne déclenche pas une nouvelle ronde de détection de changement, ce qui signifie que cette modification ne sera détectée que lorsque une prochaine ronde de détection de changement sera déclenchée ailleurs.Le point à retenir: tout ce qui modifie une liaison doit déclencher une ronde de détection des modifications lorsqu'il le fait.
Mise à jour en réponse à toutes les demandes d'un exemple de la façon de procéder : @ La solution de Tycho fonctionne, comme le font les trois méthodes de la réponse @MarkRajcok. Mais franchement, ils se sentent tous laids et mauvais pour moi, comme le genre de hacks sur lesquels nous nous sommes habitués en ng1.
Certes, il y a parfois des circonstances où ces hacks sont appropriées, mais si vous les utilisez sur quelque chose de plus qu'une très occasionnelle, il est un signe que vous vous battez le cadre plutôt que d' embrasser pleinement sa nature réactive.
À mon humble avis, une "façon angulaire2" plus idiomatique d'approcher cela est quelque chose comme: ( plunk )
la source
detectChanges()
.updateMessage
, passetMessage
ngAfterViewChecked()
travaillé pour moi:la source
J'ai corrigé cela en ajoutant ChangeDetectionStrategy à partir du noyau angulaire.
la source
CheckOnce
( documentation )this.cdr.detectChanges();
après tout ce que vous essayez de charger. Parce que c'est peut-être parce que la détection de changement ne se déclenche pasVous ne pouvez pas utiliser
ngOnInit
car vous venez de changer la variable membremessage
?Si vous souhaitez accéder à une référence à un composant enfant
@ViewChild(ChildComponent)
, vous devez en effet l'attendre avecngAfterViewInit
.Un correctif sale est d'appeler le
updateMessage()
dans la boucle d'événement suivante avec par exemple setTimeout.la source
Pour cela, j'ai essayé les réponses ci-dessus, beaucoup ne fonctionnent pas dans la dernière version d'Angular (6 ou ultérieure)
J'utilise le contrôle du matériel qui a nécessité des modifications après la première liaison effectuée.
Ajouter ma réponse ainsi, cela aide certains à résoudre un problème spécifique.
la source
L'article Tout ce que vous devez savoir sur l'
ExpressionChangedAfterItHasBeenCheckedError
erreur explique le comportement en détail.Le problème avec votre configuration est que le
ngAfterViewInit
hook de cycle de vie est exécuté après la détection des modifications mises à jour DOM traitées. Et vous modifiez effectivement la propriété utilisée dans le modèle dans ce hook, ce qui signifie que DOM doit être restitué: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 alternatives pour le résoudre:
mettre à jour la propriété de manière asynchrone soit en utilisant
setTimeout
,Promise.then
ou asynchrone observable dans le modèle référencéeffectuer la mise à jour de propriété dans un hook avant la mise à jour DOM - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.
la source
Il vous suffit de mettre à jour votre message dans le bon crochet de cycle de vie, dans ce cas, c'est
ngAfterContentChecked
au lieu dengAfterViewInit
, car dans ngAfterViewInit, une vérification du message 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:
voir la démo de travail sur Plunker.
la source
@ViewChildren()
compteur basé sur la longueur qui était rempli par un observable. C'était la seule solution qui a fonctionné pour moi!ngAfterContentChecked
etChangeDetectorRef
d'en haut a fonctionné pour moi. SurngAfterContentChecked
appelé -this.cdr.detectChanges();
Cette erreur survient car la valeur existante est mise à jour immédiatement après avoir été initialisée. Donc, si vous mettez à jour une nouvelle valeur après le rendu de la valeur existante dans DOM, cela fonctionnera correctement.Comme mentionné dans cet article Débogage angulaire "L'expression a changé après avoir été vérifiée"
par exemple, vous pouvez utiliser
}
ou
Comme vous pouvez le voir, je n'ai pas mentionné le temps dans la méthode setTimeout. Comme il s'agit d'une API fournie par le navigateur, et non d'une API JavaScript, cela s'exécutera séparément dans la pile du navigateur et attendra la fin des éléments de la pile d'appels.
La façon dont l'API du navigateur suscite le concept est expliquée par Philip Roberts dans une vidéo Youtube (Qu'est-ce que le hack est une boucle d'événement?).
la source
Vous pouvez également placer votre appel à updateMessage () dans la méthode ngOnInt () -, au moins cela fonctionne pour moi
Dans RC1, cela ne déclenche pas l'exception
la source
Vous pouvez également créer un minuteur à l'aide de la
Observable.timer
fonction rxjs , puis mettre à jour le message dans votre abonnement:la source
Il génère une erreur car votre code est mis à jour lorsque ngAfterViewInit () est appelé. Signifie que votre valeur initiale a été modifiée lorsque ngAfterViewInit a lieu, si vous appelez cela dans ngAfterContentInit (), cela ne générera pas d'erreur.
la source
Je n'ai pas pu commenter le message 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 son composant enfant (composant muet), essayez de l'ajouter également au parent.
Cela a corrigé le bug, mais je me demande quels sont les effets secondaires de cela.
la source
J'ai eu une erreur similaire en travaillant avec datatable. Ce qui se passe, c'est lorsque vous utilisez * ngFor dans une autre table de données * ngFor, jetez cette erreur car elle intercepte le cycle de changement angulaire. SO au lieu d'utiliser datatable dans datatable, utilisez une table régulière ou remplacez mf.data par le nom du tableau. Cela fonctionne bien.
la source
Je pense qu'une solution des plus simples serait la suivante:
(static working: boolean)
dans la classe où cette fonction existe et chaque fois que vous appelez la fonction, rendez-la simplement vraie comme vous le souhaitez. Dans la fonction, si la valeur de travail est vraie, revenez tout de suite sans rien faire. sinon, effectuez la tâche que vous souhaitez. Assurez-vous de changer cette variable sur false une fois la tâche terminée, c'est-à-dire à la fin de la ligne de codes ou dans la méthode d'abonnement lorsque vous avez terminé d'affecter des valeurs!la source