Angular: ExpressionChangedAfterItHasBeenCheckedError: l'expression a changé après avoir été vérifiée sur ng 4

Créé le 16 juin 2017  ·  201Commentaires  ·  Source: angular/angular

Je soumets 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

Comportement actuel


Après avoir utilisé un objet observable dans mon modèle en utilisant async, je reçois:
ERREUR Erreur: ExpressionChangedAfterItHasBeenCheckedError: Expression a changé après avoir été vérifiée. Valeur précédente: ''. Valeur actuelle: "quelque chose"

Comportement prévisible

Veuillez nous parler de votre environnement

<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

Commentaire le plus utile

J'obtiens le même problème après la mise à niveau de 4.1.3 à 4.2.3. L'utilisation de setTimeout résout le problème.

de:

PanictUtil.getRequestObservable (). Subscribe (données => this.requesting = données);

à:

PanictUtil.getRequestObservable (). Subscribe (données => setTimeout (() => this.requesting = données, 0));

Tous les 201 commentaires

Eu ce même problème avec l'utilisation de redux. Voici un lien vers le problème que j'ai publié sur leur référentiel. Quand sur angulaire 2, il n'y a pas d'erreurs mais sur angulaire 4.2.2 j'obtiens l'expression modifiée après avoir vérifié les erreurs.
https://github.com/angular-redux/store/issues/428

Le problème apparaît pour moi sur mes composants lors du passage de 4.1.3 à 4.2.x. Revenir à 4.1.3 résout le problème pour moi, mais ce n'est pas quelque chose que je veux garder pour toujours.

Je pourrais reproduire sur un projet minimal, j'essaie toujours de comprendre comment corriger cette (peut-être) régression. Mais on dirait que la vérification sale a été appliquée dans le développement. mode à partir de 4.2.x. Mais le journal des modifications n'est pas clair à ce sujet. Toute astuce bienvenue.

J'obtiens le même problème après la mise à niveau de 4.1.3 à 4.2.3. L'utilisation de setTimeout résout le problème.

de:

PanictUtil.getRequestObservable (). Subscribe (données => this.requesting = données);

à:

PanictUtil.getRequestObservable (). Subscribe (données => setTimeout (() => this.requesting = données, 0));

@jingglang pourriez-vous essayer de reproduire le problème sur http://plnkr.co/edit/tpl : AvJOMERrnz94ekVua0u5 avec le moins de code possible?

J'obtiens le même problème - mis à niveau de 2.4 à 4.2, et une partie de ma logique de masquage / affichage est maintenant cassée. C'est une logique de style accordéon, où la visibilité dépend de la comparaison du panneau actuel avec un observable qui contient quel panneau doit être visible (entre autres). Le composant utilise @select pour obtenir l'observable, et il est lié avec | async dans le modèle - a fonctionné comme un charme dans 2.4, obtenez maintenant ExpressionChangedAfterItHasBeenCheckedError et les panneaux ne se cachent pas et ne s'affichent pas au premier clic, seulement au second.

Je reçois le même problème lors de la mise à jour de 4.1.3 à 4.2.2.

Mais, j'ai trouvé une solution de contournement.
Injection de ChangeDetectionRef , et appel de la fonction detectionChanges() au point d'erreur.

constructor(private cdr: ChangeDetectionRef) {
}

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

Je pense que c'est un problème avec le changement suivant apporté à 4.2.

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

pour résoudre ce problème: https://github.com/angular/angular/issues/14321

Avez-vous du contenu dans une balise ng-content ?

J'ai le même problème avec un formulaire à l'intérieur d'une balise ng-content qui comporte maintenant des erreurs avec l'erreur ContentChangedAfter.

Je reçois le même problème lors de la mise à jour de la version 4.1.3 à 4.2.0 ou supérieure

J'attache un petit projet pour reproduire le bug

app-error.zip

Ensuite, j'ai rétrogradé à 4.1.3 (comme conseillé ci-dessus) et l'erreur disparaît, donc quel que soit le conflit, c'est spécifique à 4.2. Je n'ai pas le temps de créer un plunkr cette semaine (ou la prochaine), mais cela semble être lié à une seule action mettant à jour deux observables distincts sur le magasin, chacun ayant un impact sur l'interface utilisateur. L'une des mises à jour de l'interface utilisateur se produit, l'autre provoque l'exception.

Hmmm,
Le retour à la version 4.1.3 résout définitivement le problème et applique également le délai d'expiration, comme indiqué par @jingglang.

salut @tytskyi. J'ai fait un plunkr http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT pour ce problème. espérons que cela aidera.

la solution de contournement donnée par @jingglang fonctionne (pour moi) sur 4.2.0 ou supérieur
OU changer l'événement émetteur de l'enfant dans son constructeur ne lève plus l'erreur

@umens Vous mettez à jour le composant parent après sa vérification, vous ne conservez donc pas le flux de données unidirectionnel

@alexzuza comment puis-je y parvenir? Je l'ai toujours fait de cette façon sans l'erreur. Pourquoi mettre le SetTimout ne soulève plus l'erreur. (J'ai mis à jour le plunkr) Je doute que cela interfère avec le cycle de vie ou est-ce?

salut @umens merci pour votre temps. Le problème est ce que dit http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm?p=preview (notez que j'ai supprimé le service, pour réduire le code).
Pourquoi ça a marché? Probablement par accident l'ancienne version d'angular ou de Rxjs avait un bug ici. Pouvez-vous nous dire quelles versions ont été utilisées? Ou même mis en plunker de travail?

Pourquoi mettre le SetTimout ne soulève plus l'erreur.

Parce que vous changez de champ de manière asynchrone, ce qui respecte le flux de données à sens unique.

Considérons le contraire: pourquoi l'erreur est-elle soulevée? La première vérification de la détection des changements va de haut en bas. Il vérifie la app.toolsConfig liée au modèle. Ensuite, il rend <child-cmp> qui met app.toolsConfig jour de manière synchrone app.toolsConfig . Le rendu est terminé. Maintenant, il exécute une deuxième vérification (en mode développement uniquement) de la détection des changements pour s'assurer que l'état de l'application est stable. Mais app.toolsConfig avant le rendu de l'enfant n'est pas égal à app.toolsConfig après. Tout cela se produit pendant un «tour» unique, un «cycle» de détection de changement.

D'accord. merci pour la réponse détaillée. Je n'ai pas pu trouver d'informations sur le moment où le cycle de vie des composants enfants se produit.
pour la précédente version «fonctionnelle», j'ai utilisé:
@ angular / *: 4.1.1 (sauf compiler-cli -> 4.1.0)
rxjs: 5.3.1

@umens ici est une erreur reproduite avec les anciennes versions que vous avez mentionnées: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview. Il jette, donc probablement une dépendance à un tiers a affecté le comportement ou une instruction de reproduction incomplète?

ne peut pas dire.
voici mon yarn.lock si vous souhaitez aller plus loin: https://pastebin.com/msARLta1
mais je ne sais pas ce qui peut être différent

Je vois des erreurs "ExpressionChanged ..." similaires après le passage de 4.1.2 à 4.2.3 .

Dans mon cas, j'ai deux composants qui interagissent via un service. Le service est fourni via mon module principal, et ComponentA est créé avant ComponentB .

Dans 4.1.2 , la syntaxe suivante fonctionnait très bien sans erreur:

Modèle ComponentA:

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

Classe ComponentB:

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

Dans 4.2.3 , je dois modifier le modèle de ComponentA comme suit pour éviter l'erreur "ExpressionChanged ...":

Modèle ComponentA:

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

J'essaie toujours de comprendre pourquoi cela ne fait que devenir un problème et pourquoi le passage de *ngIf à [hidden] fonctionne.

J'ai le même problème, et cela se produit principalement lorsque j'utilise un tube asynchrone comme celui-ci:
<div>{{ (obs$ |async).someProperty }}</div>
Si je l'utilise comme ça:

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

Ensuite, l'erreur a disparu, donc je ne suis pas si sûr que ce soit uniquement parce que beaucoup de gens ont fait une erreur qui n'avait pas été détectée auparavant: je pense qu'un bogue a été introduit dans la version 4.2 ...

Le même problème pour moi. Le problème semble provenir de @ angular / router

Je suis également arrivé à la conclusion qu'il est lié au routeur. J'ai essayé d'en publier autant hier, mais il semble que le message ait échoué. J'ai étudié plus en détail et simplifié le code pour voir si je pouvais localiser l'erreur. Le code suivant fonctionne parfaitement lorsque la source ultime de l'action est un clic sur un en-tête de panneau, mais échoue avec l'erreur ExpressionChanged lorsqu'il est déclenché via le routage.

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

Cela m'a aidé à résoudre le problème! Vous devez déclencher explicitement le changement dans le parent.

Comme @acidghost , je pourrais résoudre ce problème en exécutant ChangeDetectorRef#detectChanges dans AfterViewInit depuis mon composant parent.

Si vous avez des repro plunkr, je suis prêt à le vérifier car nous avons eu des plaintes similaires sur gitter et cela a toujours été un problème d'utilisateur.

Je ne peux pas être sûr si un bogue a été introduit dans 4.2 ou si le bogue était plutôt le manque d'erreur sur <4.2 et il a été résolu sur 4.2.

Erreur: ExpressionChangedAfterItHasBeenCheckedError
Solution:
componentRef.changeDetectorRef.detectChanges ();

Voici un morceau de mon code (composants générés dynamiquement):
componentRef = viewContainerRef.createComponent (componentFactory); // le composant a été créé
(componentRef.instance) .data = input_data; // changer manuellement les données des composants ici
componentRef.changeDetectorRef.detectChanges (); // cela entraînera une détection de changement

Pour les composants simples, recherchez simplement sur Google comment accéder à "componentRef", puis exécutez
componentRef.changeDetectorRef.detectChanges ();
après avoir défini / modifié les données / le modèle des composants.

Solution 2:
J'obtenais à nouveau cette erreur en ouvrant une boîte de dialogue "this.dialog.open (DialogComponent)".
donc mettre ce code de dialogue ouvert dans une fonction, puis appliquer setTimeout () sur cette fonction a résolu le problème.
ex:
openDialog () {
laissez dialogRef = this.dialog.open (DialogComponent);
}
ngOnInit (): void {
setTimeout (() => this.openDialog (), 0);
}
bien que cela ressemble à un hack .. fonctionne !!!

Depuis 4.2, j'ai le même problème et aucune solution ou solution de contournement n'a fonctionné pour moi: (Je crée un composant dynamiquement avec une fabrique de composants dans une méthode ngAfterViewInit. Ce composant utilise une directive qui a un champ @Input() . Il semble que le champ @Input() de la directive ne soit pas défini jusqu'à ce que la vue du composant ait été construite et vérifiée, puis initialisée avec la chaîne statique.

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 {
}

ERREUR Erreur: ExpressionChangedAfterItHasBeenCheckedError: Expression a changé après avoir été vérifiée. Valeur précédente: 'indéfini'. Valeur actuelle: 'test'. Il semble que la vue a été créée après que son parent et ses enfants ont été vérifiés. A-t-il été créé dans un hook de détection de changement?

Je suis un peu confus en lisant ce fil. Est-ce un bogue connu, qui sera résolu dans une prochaine version? Ou est ce comportement attendu? Si oui, quelle est la solution? Je vous remercie.

Aucune idée si c'est lié ou non, mais je déteste ce message avec passion! cela apparaît UNIQUEMENT pour moi lors des tests unitaires avec la commande «ng test» prête à l'emploi. Je suppose que je pourrais juste être confus sur la façon de réussir à se moquer d'un service simple. Aussi surpris que personne d'autre n'ait encore vu cela lors des tests, je ne trouve rien de lié au test. Je vais regarder ce sujet et toute recherche pour ExpressionChangedAfterItHasBeenCheckedError + test unitaire.

J'ai essayé de créer un plnkr et j'ai trouvé très difficile de reproduire l'erreur - cependant, un débogage approfondi m'a, je pense, m'a rapproché un peu plus de voir ce qui ne va pas (mais pas de décider s'il s'agit d'un bogue, ou une erreur de ma part).

J'ai créé un petit exemple d'application en utilisant des versions réduites des mêmes réducteurs combinés dans le même ordre que dans l'application réelle, avec le dernier routerReducer. À ma grande frustration, l'erreur ne s'est pas produite. J'ai donc défini des points d'arrêt et observé le flux d'actions à travers les réducteurs. Ce que j'ai trouvé, c'est que les deux applications envoyaient des actions dans l'ordre inverse.

Mon réducteur d'interface utilisateur gère la visibilité et est la source du changement d'état qui déclenche l'erreur. Voici une version réduite du réducteur, montrant les actions pertinentes:


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;
    }
}

Lorsque je l'exécute dans ma petite application de test, cela fonctionne - d'abord, l'action LIST_INGREDIENTS se déclenche et le nouvel état est retourné; puis l'action @ angular-redux / router :: UPDATE_LOCATION se déclenche et le cas par défaut est atteint, renvoyant le même état.

Lorsque je l'exécute dans l'application réelle, l'ordre ou les actions sont inversés. L'action @ angular-redux / router :: UPDATE_LOCATION se déclenche en premier, renvoyant l'ancien état par défaut. Ensuite, LIST_INGREDIENTS se déclenche, renvoyant le nouvel état (modifié) - et l'erreur est levée.

Je suis complètement ouvert à l'idée que j'ai fait quelque chose de stupide et que ce n'est pas un bug réel - mais si oui, est-ce que quelqu'un d'autre voit ce que j'ai fait?

(En note de bas de page, j'ai vérifié la version 4.1.3 ... et elle déclenche également les réducteurs de redux dans le `` bon '' ordre - c'est-à-dire que le changement d'emplacement se déclenche après l'action de la liste des ingrédients, ce qui explique probablement pourquoi cela fonctionne, mais pas la version 4.2).

Souffrant du même bogue ennuyeux lorsqu'un composant change la propriété du service. Résolu en déplaçant * ngIf vers le composant parent. Donc c'est définitivement un bug

a pu contourner ce problème en utilisant la méthode ngDoCheck () et en appelant la méthode ChangeDetectorRef.detectChanges.

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

@pappacurds Ne faites pas ça. Vous avez provoqué une infinité de cycles de détection des changements avec cela.

pourrait être la même erreur ici https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb?p=preview

erreur: previous undefined after undefiend

après la mise à jour même erreur

Pareil pour moi

Tout va bien dans 4.0.0, après la mise à niveau vers 4.2.6, j'obtiens la même erreur.

L'article Tout ce que vous devez savoir sur l'erreur ExpressionChangedAfterItHasBeenCheckedError explique en détail pourquoi cette erreur se produit et les correctifs possibles

les correctifs sont évidents, mais changer le cadre de telle sorte que la mise à jour entraîne la réécriture des applications utilisateur pour «prendre en charge» une nouvelle mise à jour mineure de la plate-forme est câblé. JS conserve encore de vieilles erreurs pour maintenir la compatibilité ...

Même erreur ici

Je vois également ce problème dans ~4.2.4 . Le passage à la version ~4.1.3 résolu le problème.

Le problème est lié à la modification des valeurs de ContentChildren , même lors de l'utilisation de ChangeDetectorRef detectChanges() à la fin de AfterViewInit du parent (où il effectuait les modifications). Curieusement, ce problème se produit uniquement avec les composants et non les directives. J'ai converti une directive que j'avais travaillé dans 4.2.4 en un composant avec un modèle de <ng-content></ng-content> , puis il a commencé à lancer l'erreur ExpressionChangedAfterItHasBeenCheckedError .

J'ai obtenu cela en travaillant avec le contrôle d'entrée du matériau 2. J'ai essayé d'en définir la valeur

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

J'ai mis en place un simple Plunker montrant un cas où les erreurs ExpressionChangedAfterItHasBeenCheckedError ne commencent à apparaître qu'à partir de Angular 4.2.x .

J'ai créé un simple plunker montrant l'erreur lors de la modification d'une valeur de ContentChild même en utilisant ChangeDetectorRef detectChanges() dans le composant parent ngAfterViewInit .

Edit: a également créé un plunker simple basé sur mon premier exemple, mais avec une directive parente au lieu de Component et aucune erreur.

@saverett Je vois un message d'erreur différent avec votre plunk

@JSMike J'ai eu exactement le même cas que dans votre plunker (contourné en enveloppant le code référençant ContentChild dans setTimeout , plunker )

@JSMike Merci pour la mise en

@matkokvesic ce n'est pas vraiment une solution, cela provoque simplement le changement en dehors du crochet du cycle de vie. L'exemple de code fourni ne provoque pas d'erreur lors de l'utilisation d'Angular v4.1.3

@JSMike Je suis d'accord, c'est une solution temporaire. L'utilisation d'éléments référencés par ContentChildren dans ngAfterViewInit fonctionnait bien avant Angular 4.2.6.

Je l'ai corrigé en utilisant changeDetection: ChangeDetectionStrategy.OnPush et this.changeDetector.markForCheck(); dans mon code

@touqeershafi si vous utilisez this.changeDetector.markForCheck(); cela ne signifie pas que vous n'utilisez pas les choses de la bonne manière

Cela se produit encore. Ceci est définitivement un bogue, pas un comportement attendu et devrait être corrigé.

@istiti je comprends que ce n'est pas une bonne façon, mais au moins vous pouvez vous débarrasser de cette erreur temporairement, mais en ce qui concerne mon projet, j'ai déjà ajouté le TODO avec l'URL du bogue.

Cela se produit encore. Ceci est définitivement un bogue, pas un comportement attendu et devrait être corrigé.

Je pense que je dois être d'accord avec @rwngallego ... Avant la mise à jour 4.2.x, j'avais déjà écrit un certain nombre de composants stlye réutilisables / ng-content qui dépendent de @ContentChildren et de créer des relations entre parent et enfant à travers ceux-ci canaux (obtention / définition des propriétés et ainsi de suite).

Je me retrouve constamment à devoir injecter l'instance ChangeDetectorRef dans tous ces constructeurs (y compris les classes enfants qui étendent ces composants), et cela ne ressemble pas à un comportement voulu ou correct.

Pour ceux qui utilisent un composant dynamique comme @saverett (l'utilisation d'un routeur utilise un composant dynamique), je pense que c'est le problème indiqué ici https://github.com/angular/angular/issues/15634.

Pour quiconque fait des changements dans ngAfterViewInit / ngAfterViewChecked je pense que c'est attendu.

Pour les autres, (je n'en ai pas encore vu ici) c'est encore un mystère pour moi.

Cela se produit pour moi dans le scénario suivant.
Avoir un composant parent qui utilise un composant enfant. Le composant enfant a besoin de données que le parent fournira, mais ces données proviennent d'un résultat de promesse dans le constructeur parent. Alors:

parent

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

Modèle parent

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

composant enfant

@Input() source:any[];

Ce code génère cette erreur, mais l'ajout de ceci au composant parent a résolu le problème:

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

Est-ce maintenant une solution recommandée ou est-ce une solution de contournement pour un bogue et quelles seraient les implications d'un tel changement? pour moi, il semble que vous disiez à Angular de détecter les changements, ce que je pense que vous devriez éviter, alors ...

@sulhome ... votre problème est que la promesse dans le constructeur du parent change @Input sur le composant enfant ... pendant le cycle du CD et sa confirmation en mode dev. Telle est la raison de l'erreur.

La ligne this.cdRef.detectChanges(); ajoute le deuxième cycle de CD donc la confirmation après qu'il est réussi et => pas d'erreur.

Donc tout fonctionne comme prévu. Il est important de comprendre comment cela fonctionne pour utiliser les bonnes constructions et la logique générale dans votre code pour éviter ce type de problèmes. Vous pouvez lire à ce sujet dans l'explication détaillée ici: https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

@ mlc-mlapis donc vous dites que je fais les choses correctement en ajoutant this.cdRef.detectChanges(); et que cela n'a aucune implication?
J'ai pensé qu'en écoutant ngOnChanges sur l'enfant, je devrais choisir le changement dans @Input du parent. n'est-ce pas la bonne approche pour le faire? Veuillez noter que j'ai fait cela et que je recevais toujours l'erreur, mais je pensais que c'était pour cela que ngOnChanges est conçu

@sulhome Oui, this.cdRef.detectChanges(); est ce qui aide mais cela a aussi pour conséquence que vous avez 2 CD maintenant ... donc cela dépend comment et où vous utilisez la stratégie OnPush sur les composants pour éliminer les frais généraux de encore un cycle de CD.

S'il est vrai que dataservice.getData prend des données en utilisant http, alors la solution la plus simple serait probablement d'avoir un observable dans ce service et de s'y abonner à partir du composant enfant. Ainsi, lorsque les données seront émises sur le flux, l'enfant peut y réagir.

@ghetolay pour la

J'ai ce problème lorsque j'obtiens des données d'un composant enfant avec @Output.

au composant parent home.ts

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

isactive est modifié lorsque dragstart et dragend ....

le message d'erreur est
ExpressionChangedAfterItHasBeenCheckedError: l'expression a changé après avoir été vérifiée. Valeur précédente: "true". Valeur actuelle: 'false'.

ou y a-t-il d'autres moyens de modifier / ajouter un attribut de classe?

Même problème dans 4.2.x et 4.3.0 mais pas 4.1.x.

L'erreur se produit uniquement sur les routes qui utilisent ng-template .

Solution: Mettre tout le code de ngAfterViewInit dans setTimeout résolu le problème pour moi (merci / crédits à @jingglang).

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

@Wernerson , jetez un œil au commentaire de JSMike sur setTimeout. Il est important de noter que vous modifiez simplement le code en dehors du hook de cycle de vie.

Mon problème était que j'ai utilisé ActivatedRoute de params Observable pour modifier un état dans le ngOnInit() . Il semble que le Router émette la synchronisation params , donc la mise à jour se produit pendant le CD.
Ma solution simple pour cela est actuellement:

  // !!! 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));
    });

(Je sais que ce n'est pas le plus beau code réactif / fonctionnel de tous les temps;))
La solution était donc de delay(0) l'émission du paramètre par un tick .

J'espère que je peux aider d'autres personnes ayant des problèmes similaires.
De plus, toutes les préoccupations sont les bienvenues, comment ma solution est erronée ou dangereuse.

Quel est le statut de ce problème? C'est toujours apparent dans Angular 4.2.6.
Est-ce un problème dans les versions de production? Comme cette erreur ne se produira que dans les versions de débogage.

Je déteste toutes les solutions de contournement avec l'exécution manuelle de la détection des changements, ou même de l'exécuter dans un setTimeout () ...

setTimeout() est inefficace, car il provoque un nouveau cycle de détection des modifications sur l'ensemble de l'application, tandis que detectChanges() ne vérifie que le composant AFAIK actuel.

Mon équipe avait une méconnaissance fondamentale de ce qu'est et n'est pas ngOnInit. Les documents disent

ngOnInit() : Initialise la directive / composant après qu'Angular affiche d'abord les propriétés liées aux données et définit les propriétés d'entrée de la directive / composant. Appelé une fois, après le premier ngOnChanges ().

Nous nous abonnions à nos services (qui gardent une trace de l'état de l'application) à partir de ngOnInit . Nos abonnements sont des BehaviorSubjects Voici un Plunker très simple illustrant que c'est un problème.

L'exemple de Tour Of Heroes utilise finalement http qui est asynchrone et ne cause donc pas de problèmes. Je pense que c'est la source de notre confusion.

Ce que nous faisons maintenant, c'est nous abonner à nos états synchrones à partir de notre constructeur à la place, afin que les valeurs de nos composants soient définies immédiatement. D'autres dans ce fil ont suggéré de lancer une deuxième détection de changement via des délais d'expiration, de définir des valeurs plus tard ou de dire explicitement à angular de le faire. Cela empêchera l'erreur d'apparaître, mais à mon avis, c'est probablement inutile.

L'article que @maximusk a partagé est une lecture incontournable : Tout ce que vous devez savoir sur l'erreur ExpressionChangedAfterItHasBeenCheckedError .

Si rien d'autre, ce fil devrait résulter en un avertissement détaillé de tout cela dans la documentation angulaire officielle.

L'article que @maximusk a partagé est un must: Tout ce que vous devez savoir sur l'erreur ExpressionChangedAfterItHasBeenCheckedError.

Je vous remercie

@adamdport

Cela fonctionne-t-il dans le cas de l'utilisation de @ViewChildren @ContentChildren pour modifier les propriétés enfants du parent, puisque les enfants ne sont pas disponibles tant que le cycle de vie ngAfterViewInit () n'a pas été exécuté?

Veuillez garder à l'esprit qu'une partie de ce problème est un bogue confirmé et non une erreur de l'utilisateur:
https://github.com/angular/angular/issues/15634

Le problème résolu par @saverett est correct, l'utilisation de *ngIf provoquait l'erreur.

*en train de regarder...

Cela peut se produire si vous passez des objets à une boîte de dialogue Matériau angulaire, puis dans la boîte de dialogue, utilisez-les directement plutôt que de faire this.myDataInput = Object.assign({}, dialogDataInput) . Ie this.myDataInput = dialogDataInput; et ensuite faire quelque chose comme this.myDataInput.isDefault = !this.myDataInput.isDefault; . Comme expliqué ci-dessus, le composant enfant (boîte de dialogue) modifie les valeurs qui sont exposées dans la vue du composant parent.

remplacer l'attribut par [attribut]

Est-ce que quelqu'un sait avec certitude s'il s'agit d'une erreur intentionnelle ou d'un bogue?
N'est-il pas correct de charger des données dans ngOnInit? Parce que si je déplace le code vers le constructeur (chargement via un Observable), l'erreur n'est pas levée ...

@Wernerson . Avez-vous * ngIf dans votre html? méfiez-vous que les choses vont être rendues après que ngOnInit soit terminé, solong, tout à l'intérieur de * ngIf ne fonctionnera pas et pourrait conduire à cette erreur.

Il faut le retravailler. Avec "ngIf" et les contrôles personnalisés, vous obtenez ce message d'erreur. Un délai d'attente n'est certainement pas une solution.

@ j-nord si vous lisez les commentaires, vous saurez que timeout n'est pas requis.

@ j-nord / @dgroh J'ai vécu la même chose. J'ai utilisé un ng-hint qui utilise une directive ngIf et je ne peux pas me débarrasser de cette erreur. Avez-vous réussi à mettre en œuvre une solution de contournement?

@Wernerson oui, une partie de ce problème est une erreur intentionnelle ou un bogue: https://github.com/angular/angular/issues/15634

@ mawo87 cela a fonctionné pour moi avec hidden car il ne supprime pas l'élément du DOM. Pour notre exigence, c'était correct et c'était aussi une solution propre.

@ mawo87
Vous devez d'abord trouver la variable qui est signifiée par le message d'erreur. @Angular Team : Ce serait bien d'avoir le nom de la variable dans le message d'erreur. Ce n'est pas facile à trouver avec des masques plus grands et Webpack. Si vous avez trouvé la commande là où elle se produit, la commande dans la ligne suivante vous aidera:

this.cdr.detectionChanges();

Voir l'exemple de vermilion928 du 19 juin.

Merci @dgroh et @ j-nord.
@ j-nord Je regardais également la méthode detectionChanges, mais je ne pourrais pas utiliser cette approche car dans l'article suivant, il était déconseillé:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

Dans mon cas, j'utilise un service PageTitle et je m'y abonne dans le composant parent, puis lorsque les composants enfants sont chargés dans la vue, ils définissent le PageTitle dans le service et le parent obtient la valeur et l'affiche via l'abonnement. Cela a fonctionné sans aucune erreur dans 4.1.x, mais lève l'erreur ExpressionChangedAfterItHasBeenCheckedError dans 4.2x et versions ultérieures.

Tout fonctionne toujours comme prévu et les erreurs ne s'affichent pas dans les versions déployées / de production (en raison de l'absence d'un deuxième cycle de détection des changements), mais en développement, il y a énormément de rouge dans ma console.

Je ne sais toujours pas quelle est la solution appropriée (même à court terme). Mes abonnements aux composants parents sont dans le constructeur et les mises à jour des composants enfants via le service sont dans ngOnInit.

J'ai lu le post `` Tout ce que vous devez savoir sur ... '', qui était informatif, mais ne me laisse pas croire que je fais quelque chose de mal et ne fournit pas de solution fonctionnelle au problème.

@alignsoft si vous définissez le pagetitle dans les composants enfants dans le ngOnInit, alors je crois que l'erreur est valide ... Voir l'article qui était lié auparavant.

@alignsoft Je suis d'accord, il y a eu beaucoup de discussions autour de cette erreur, mais sans véritable instruction claire sur sa validité ou non ... est-ce plus un "avertissement" pour le développeur sur les normes et la liaison / manipulation des données?

Dans mon cas spécifique, je m'occupe de composants parents / enfants utilisant @ViewChild et @ContentChild , je dois appeler detectChanges () encore et encore pour empêcher l'erreur de s'afficher dans la console ...

@rolandoldengarm J'ai essayé de m'abonner dans le constructeur parent et de définir les valeurs dans AfterViewInit dans les composants enfants et d'obtenir toujours l'erreur. Est-ce la bonne façon de gérer cela en fonction du cycle de détection des flux et des modifications, et l'erreur est potentiellement un bogue?

S'il vous plaît, ce serait génial d'avoir une sorte de réponse officielle ici ...

En regardant cela à nouveau. Ressemble à mon exemple de https://github.com/angular/angular/issues/17572#issuecomment -315096085
aurait dû utiliser AfterContentInit au lieu de AfterViewInit en essayant de manipuler ContentChildren dans le cadre du cycle de vie du composant.

Voici le plnkr mis à jour avec AfterContentInit et plus d'erreur: http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@JSMike J'ai changé AfterViewInit en AfterContentInit , toujours la même erreur. Même si je mets à jour mes données dans OnInit l'erreur se produit. Autant que je sache, c'est selon le cycle de vie angulaire correct (non?) D'initialiser les données pendant OnInit . Avec le fait que cette erreur ne s'est pas produite dans les versions précédentes, je suppose qu'il s'agit d'un bogue. Cependant, j'aimerais avoir une sorte de déclaration de l'équipe de développement si c'est le cas ..? :RÉ

@Wernerson, vous ne devriez pas mettre à jour les données dans ngOnInit moins que les données ne soient disponibles à ce hook de cycle de vie. Vous devriez déplacer votre logique pour qu'elle soit dans ngAfterContentInit si elle a à voir avec des enfants de contenu.

@JSMike N'a pas aidé, l'erreur persiste. Je viens de m'abonner à un changement d'état dans l'application après avoir reçu les données demandées (de manière asynchrone) dans ngAfterContentInit .

@Wernerson avez-vous un exemple de plnkr?

Échantillon de base avec StackBlitz
Un composant enfant émettant un événement et un composant parent mettant à jour une variable locale.
Obtenir une erreur dans la console js:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

Trouvé, le ngAfterContentInit ne fonctionne que si les enfants sont statiques, il échoue toujours lors de la création d'enfants en utilisant *ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@soldiertt vous avez juste besoin de detectChanges () https://stackblitz.com/edit/angular-qfu74y

@JSMike si je fais cela, ngAfterViewInit du composant enfant n'est pas déclenché: https://stackblitz.com/edit/angular-bdwmgf

@soldiertt cela semble étrange, mais ressemble à un problème distinct qui devrait être signalé.

@JSMike Ma configuration est assez complexe avec des services, des bibliothèques tierces, des informations confidentielles, etc. etc. Malheureusement, je n'ai pas pu reproduire le bogue dans un plunker: /

L'injection de ChangeDetectorRef a fonctionné pour moi!

J'ai ce problème dans un carrousel, car je dois définir une propriété sur le composant d'élément de carrousel, propriété qui n'est connue qu'après avoir été calculée dans le composant de carrousel parent. Et tous les éléments arrivent via ng-content , donc je ne peux travailler qu'avec les éléments du composant ngAfterContentInit du composant carrousel parent. Quelque chose comme:
carrousel:

@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
  });
}

article:

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

Outre le hack setTimeout() , la seule autre solution serait-elle d'utiliser un service et un sujet pour faire communiquer les éléments avec le carrousel parent? Je veux dire que ce serait ridicule ... Cela voudrait dire que si j'ai 100 articles dans un carrousel, alors j'aurais 100 abonnements. Et cela double si j'ai 2 carrousels. Cela ne peut sûrement pas être la méthode recommandée?! Droite?

Le plus souvent, nous aurions besoin de définir des propriétés sur nos composants, des propriétés qui ne peuvent être connues avant AfterContentInit ... Et dans de nombreux cas (quand nous ne parlons pas de 1 ou 2 composants comme cela, mais plutôt 50 ou 100 comme dans un carrousel) ayant des dixièmes ou des centaines d'abonnement serait ridicule.

Y a-t-il une partie vraiment cool d'Angular qui peut m'aider ici, que je n'ai pas encore découverte?

PS J'ai également essayé d'injecter ChangeDetectorRef et d'utiliser detach() à la fois sur le composant du carrousel et le composant de l'élément. Je m'attendais à ce que cela corrige l'erreur depuis, eh bien ... je dis essentiellement "détection de changement de vis, je n'en ai pas besoin du tout. S'il vous plaît, plutôt s'il vous plaît: ne détectez AUCUN changement _" ... mais l'erreur est toujours lancée! Alors, à quoi sert la méthode detach() , si elle ne fait pas ce qu'elle dit?

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

setTimeout() n'est pas nécessaire un hack c'est peut-être la manière très valable de faire des choses comme par exemple lorsque vous réagissez à la vue pour la mettre à jour, par exemple: changer les liaisons du modèle pendant ngAfterViewInit .

Le problème, c'est quand on s'en sert comme par magie sans vraiment savoir pourquoi. Mais il existe un scénario totalement valide où l'utilisation de setTimeout() est la bonne façon.

@ghetolay N'est-ce pas un piratage de sortir du cycle de vie du ou des composants que le système de détection de changement évalue? J'ai suivi la route ChangeDetectorRef.detectChanges (), mais cela me semble même un hack ... si l'une de ces solutions est "acceptable", je suppose que nous devrons vivre avec, mais cela casse vraiment tout utilisation de la modification de ViewChild (s) ou ContentChild (s)

@fugwenna

N'est-ce pas piraté de sortir du cycle de vie du ou des composants que le système de détection de changement évalue?

Je ne comprends pas cette question. sortir du cycle de vie?

Angular lancera un tour de CD à chaque fois qu'un événement asynchrone se produira (événement dom, xhr, setTimeout etc.), pendant ce CD, il mettra à jour la vue. ngAfterViewInit hook est exécuté après cela, il n'y a donc plus de CD prévu pour s'exécuter et il ne sera pas déclenché à nouveau jusqu'à ce qu'un autre événement asynchrone se produise. Utiliser setTimeout() est le moyen le plus simple de le déclencher à nouveau. Vous pourriez certainement le faire vous-même, mais je pense que les avantages sont négligeables.

@ghetolay Je fais référence au commentaire de @JSMike et à d'autres fils que j'ai lus:

@matkokvesic ce n'est pas vraiment une solution, cela provoque simplement le changement en dehors du crochet du cycle de vie. L'exemple de code fourni ne provoque pas d'erreur lors de l'utilisation d'Angular v4.1.3

Donc, je pense que l'utilisation d'un setTimeout () fera que le hook du cycle de vie sera "ignoré" ou non évalué comme il le serait normalement?

@ghetolay @fugwenna qu'en est-il de la méthode detach() du ChangeDetectorRef ? Des pensées? Cela ne devrait-il pas désactiver complètement la détection des changements, éliminant ainsi également l'erreur? Je ne comprends pas pourquoi ça ne marche pas? Ou est-ce que l'erreur est "indépendante" de cela? Cela signifie qu'Angular ne se soucie pas vraiment du fait que nous avons désactivé la détection des changements, il se soucie seulement que nous ayons changé une propriété après qu'elle a été définie?
Si les gens se disputent toujours que "ExpressionChangedAfterItHasBeenChecked" est un bogue ou non, peut-être que celui-ci est définitivement un bogue ... À mon avis, Angular ne devrait plus se soucier de nous changer les propriétés après leur définition, si nous avons complètement détection de changement désactivée. Surtout la façon dont je l'ai fait, pour le composant parent et tous les autres composants enfants, sur un arbre entier essentiellement.

Je suis un peu perdu ici, l'un des problèmes que j'ai est que je ne sais pas comment je ferais même des choses très courantes sans déclencher l'exception en mode développement.

Par exemple, j'ai écrit un composant d'accesseur de valeur de contrôle qui encapsule un sélecteur de date. Je nourris ce composant avec un modèle vide qui est passé au datepicker en interne qui l'initialise et le renvoie via la fonction enregistrée. Bien sûr, les enfants modifient le modèle lors de son initiation, mais c'est ce que je veux! Je veux récupérer un modèle initialisé mais ne pas faire l'initialisation dans le parent car c'est le datepicker qui sait comment faire cela.

Le problème est de savoir comment procéder maintenant? Appeler setTimeout et déclencher à nouveau la détection des modifications? Utilisez ChangeDetectorRef? Je n'aime pas ça car cela rend vraiment le code difficile à lire. Initialiser chose dans un service séparé? Mais ce service ne sert qu'un objectif très technique sans aucune valeur commerciale et je ne suis même pas sûr que cela résoudra le problème.

@fugwenna Ma principale préoccupation était que cela fonctionnait dans la version 4.1.3 et qu'il est maintenant cassé. Je n'ai encore vu personne dans l'équipe angulaire se demander si nous devrions utiliser setTimeout dans nos fonctions d'initialisation et laisser les zones prendre le dessus, mais cela ressemble à un anti-modèle par rapport à la façon dont cela fonctionnait auparavant.

@ kavi87 Donc, cette question de stackoverflow m'a en fait donné la solution élégante dont j'avais besoin. Ce qui était de rendre l'émetteur d'événements dans mon composant enfant asynchrone :
new EventEmitter(true)

Pour un problème qualifié de régression avec 109 commentaires, ce problème est décevant avec n'importe quel mot officiel.

Alors que tout le monde ici fait du bon travail avec les techniques d'auto-assistance, j'aimerais savoir s'il s'agit en fait d'un bogue ou simplement d'un nouveau comportement implémenté dans la version 4.2.x. Si c'est le cas, il aurait dû être répertorié comme un changement de rupture (et donc pas une version mineure?)

@MrCroft
Je ne suis pas sûr que la désactivation complète de la détection des changements pour faire taire une exception soit une bonne voie à suivre, même si en théorie, je vois comment cela devrait / pourrait avoir un sens.

Je suis d'accord avec @JSMike et @rosslavery en ce qui concerne très peu de réponse ou d'explication sur la raison pour laquelle ce message d'erreur est apparu après 4.2x sans aucune documentation sur les changements de rupture ... Cependant, un autre élément déroutant de ceci est la façon dont il est apparemment seulement jeté dans mode de développement, qui ressemble presque plus à un avertissement d'interruption de la détection de changement (?)

Im mon AppComponent j'ai un div.
Le seul but de ce div est d'afficher une superposition chargée.
L'état occupé est déclenché dans une classe de service 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$;
  }

Après la mise à niveau vers 4.3.4, j'obtiens toujours une ExpressionChangedAfterItHasBeenCheckedError lorsque le service déclenche l'état occupé.

Existe-t-il un correctif pour ce scénario asynchrone simple?

@ emazv72 ... cette démo stackblitz / plunker est-elle la même que votre cas? https://stackblitz.com/edit/angular-2p2h9s?file=app%2Fapp.component.ts ou https://plnkr.co/edit/RPOxSVnXvM1TqKp0peSY?p=preview

@mukunku Merci, je vais essayer ça.

@ mlc-mlapis J'ai légèrement changé votre plunker mon code est exactement https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb?p=preview

mais le plunker ne montre pas le problème. Je vais essayer de mettre à niveau vers la dernière version 4.x pour voir si cela a été corrigé.

Merci

Salut à tous - excuses pour le retard ici.

Donc - ce comportement fonctionne en fait comme prévu, ce qui, je me rends compte, pourrait être inattendu pour certaines personnes, alors laissez-moi essayer d'expliquer les mécanismes.

Le plunker de @saveretts en est une bonne reproduction (merci!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN?p=preview

Le tldr est qu'Angular a toujours fonctionné de cette façon - la détection des changements s'exécute, de haut en bas (ou parent -> enfant), en un seul passage. Donc, si nous avons:


@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 détection des modifications s'exécutera d'abord pour la racine de l'application, vérifiera toutes ses liaisons, puis détectera les modifications du composant enfant - encore une fois, car nous le faisons en un seul passage (plutôt que le style AngularJS continue à vérifier jusqu'à ce qu'il soit stable), la liaison parent (le * ngIf, dans ce cas) n'est pas revérifié - donc maintenant votre application est dans un état incohérent (!)

En mode de développement, nous exécutons en fait cette détection de changement deux fois - la deuxième fois qu'elle s'exécute vérifie que les liaisons n'ont pas changé (ce qui indiquerait un état incohérent), et renvoie l'erreur si un tel problème est détecté. Vous pouvez vérifier ce comportement en appelant enableProdMode() dans la repro: https://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56?p=preview. Notez que l'erreur n'est pas levée (car nous n'exécutons pas le CD x2), mais l'application se retrouve dans un état incohérent (l'enfant est affiché, mais pas le * ngIf), ce qui est une mauvaise chose.

La raison pour laquelle cela a récemment fait surface est qu'avant, le routeur exécutait un certain nombre de vérifications de détection de changement supplémentaires lors de l'insertion de composants - cela était inefficace et causait des problèmes avec les animations et le routage, en particulier lorsque les sorties de routeur sont imbriquées dans ngIfs et autres. correctement une erreur indiquant que le développeur viole le flux de données à sens unique.

Je dirais que généralement, un code qui viole ce flux à sens unique (comme le fait la repro ci-dessus) est incorrect . Cependant, dans certains cas, vous pourriez souhaiter ce comportement (en fait, nous avons une configuration similaire dans la bibliothèque de formulaires angulaires). Cela signifie que vous devez déclencher explicitement une autre exécution de détection de changement (qui reviendrait au composant racine et vérifierait de haut en bas). Appeler setTimeout parvient, bien qu'il soit généralement préférable d'utiliser une promesse pour déclencher cela - voir https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv?p=preview

J'espère que ça t'as aidé!

@robwormald Cette même erreur se produisait également avec d'autres scénarios. Je ne sais pas si cela résout le problème avec ContentChildren . Pourquoi une directive peut-elle modifier les valeurs des enfants pendant AfterContentInit sans erreur, mais faire de même dans un composant génère des erreurs?

Aussi, est-il prévu que si un cdr.detectChanges() est appelé pendant un processus d'initialisation, les autres fonctions d'initialisation finissent par ne pas être appelées? C'est quelque chose de découvert dans ce fil pour lequel j'ai créé un problème distinct .

@robwormald

Je pense que @JSMike est correct dans l'évaluation que cette erreur est toujours générée lors de scénarios où le flux unidirectionnel provient d'un parent-> enfant utilisant ContentChild (ren) et ViewChild (ren).

Est-il acceptable de considérer ce que j'ai déjà dit , c'est-à-dire que cette erreur est davantage un "avertissement" qui indique que le développeur va à l'encontre du flux unidirectionnel "meilleure pratique" mais ne provoque aucune erreur de rupture d'application, depuis la détection de changement ne fonctionne qu'une seule fois en mode production (monde réel)?

@fugwenna Non, c'est vraiment une erreur, car sinon cela laisse la vue dans un état incohérent. L'échantillon que Rob est en train de parcourir en dit long.

En outre, il y a cette citation de la documentation

La règle de flux de données unidirectionnel d'Angular interdit les mises à jour de la vue une fois qu'elle a été composée. Ces deux crochets se déclenchent une fois la vue du composant composée.

Si vous avez vraiment besoin de modifier quoi que ce soit, utilisez l'une des méthodes asynchrones pour le faire. setTimeout pourrait convenir ici, mais sachez que cela vient avec un délai (au moment d'écrire ces lignes) de 4 ms qui lui est propre. Une autre façon est Promise.resolve().then(() => Assignment = here)

Mieux encore, il faut prendre du recul et voir pourquoi vous devez apporter un changement si tard dans le processus et voir s'il existe un moyen plus propre de résoudre le problème. Si je me surprends à faire cela, je le traite comme une odeur de code.

Merci pour l'explication détaillée @robwormald. Je ne comprends toujours pas pourquoi un flux de données enfant -> parent semble toujours fonctionner lors de l'utilisation d'une liaison [hidden] plutôt que *ngIf (voir https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u?p = aperçu). Des idées pourquoi cela fonctionne toujours sans donner un ExpressionChangedAfterItHasBeenCheckedError ?

Angular utilise la fonction checkAndUpdateView pour effectuer la détection des modifications.

Une courte représentation de cette fonction pourrait ressembler à:

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);
}

Il est exécuté récursivement grâce aux méthodes execEmbeddedViewsAction et execComponentViewsAction . Et aussi le point clé ici est la vérification angulaire des vues intégrées après la vérification des directives et avant la mise à jour du DOM.

Pour simplifier l'exemple @saverett , j'utiliserai le modèle suivant

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

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

Nous savons déjà que *ngIf n'est que du sucre. Alors

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

On peut imaginer la structure suivante:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

Que fait la directive RouterOutlet ?

Il injecte le composant routé dans ViewContainerRef

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

Cela signifie que router-outlet crée EmbeddedView donc après l'initialisation de la directive RouterOutlet nous avons deux vues intégrées dans notre vue AppComponent .

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

Illustrons-le dans l'image

haschanged

Revenons maintenant à notre fonction principale

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 effectuera la vérification dans l'ordre suivant:

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

Notre directive NgIf sera vérifiée en premier (ici sharedService.displayList=false est mémorisée) puis la méthode NgOnInit intérieur de BComponent sera appelée (où nous changeons sharedService.displayList à true ) Et quand angular exécute checkNoChanges il lèvera l'erreur Expression has changed after it was checked

Remplaçons ensuite *ngIf par [hidden] . Maintenant, nous n'aurons qu'une seule vue intégrée insérée par RouterOutlet et notre [hidden]="sharedService.displayList" sera vérifié dans updateRenderer

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

Angular effectuera une vérification comme ceci:

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 cette façon, la liaison de propriété dom ne provoquera pas l'erreur car nous avons mis à jour après l'appel de ngOnInit value.

C'est la principale différence entre la directive structurelle *ngIf et la liaison de propriété dom [hidden]

Merci pour l'aperçu détaillé @alexzuza! C'était très utile!

setTimeout () a fonctionné pour moi :) Merci @jingglang :)
.subscribe (données => setTimeout (() => this.requesting = données, 0))

@robwormald a raison ici. J'ai eu le même problème et c'était plus difficile à détecter car j'utilisais NGRX et observables. Le problème que j'ai eu était qu'il y avait des conteneurs de composants à différents niveaux dans la hiérarchie des itinéraires qui s'abonnaient à l'état du magasin. Dès que j'ai déplacé mes composants de conteneur au même niveau dans mon module de fonctionnalités, le problème a disparu. Je sais que c'est en quelque sorte une réponse alambiquée, mais si vous utilisez un modèle de conteneur / composant NGRX, j'espère que cela orientera votre enquête dans la bonne direction.

Avec setTimeOut ça marche, merci @jingglang
ngAfterViewInit () {
this.innerHeight = (window.innerHeight);
setTimeout (() => this.printPosition (), 0);
}

@alexzuza , cela ne se produit pas seulement pour ngIf, cela se produit (du moins pour moi) aussi avec [ngClass]

@ Ninja-Coding-Git Je viens de décrire un cas spécifique. Je sais que cela peut arriver avec n'importe quel code.

Pour moi, appeler ChangeDetectorRef après avoir modifié l'élément a résolu le problème:

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();
  }

}

Solution trouvée ici

bonjour @thetylerb! pourriez-vous élaborer plus à ce sujet. Nous utilisons ngrx et je ne sais pas comment compenser cela. Je pense qu'utiliser des promesses en plus des observables est assez gênant.

@ piq9117 vous pouvez également jeter un oeil à ce commentaire https://github.com/angular/angular/issues/18129#issuecomment -326801192 J'ai fait sur l'interaction entre la détection des changements et ngrx, cela peut être utile je suppose

@victornoel merci! Cela décrit assez bien notre situation. Nous avons des structures de données immuables imbriquées (immutablejs), et nous utilisons des tubes asynchrones partout. Mes premières pensées étaient, le magasin changerait l'arborescence des composants lorsque le magasin aurait été hydraté, et l'arbre des composants compenserait simplement ce changement et ferait un autre changement.

Nous avons des structures de données immuables imbriquées (immutablejs), et nous utilisons des tubes asynchrones partout. Mes premières pensées étaient, le magasin changerait l'arborescence des composants lorsque le magasin aurait été hydraté, et l'arborescence des composants compenserait simplement ce changement et ferait un autre changement de détection.

Dans mon projet, nous avons une sorte de structure de données normalisée (au lieu de structures de données imbriquées), donc cela atténue un peu ce genre de problèmes, jusqu'à ce que ce ne soit pas le cas! : O

Vous espérez seulement soit:

  • la modification de vos structures de stockage de sorte que les modifications apportées à une pièce ne déclenchent pas les mises à jour des autres pièces
  • faire attention à ne pas déclencher d'événements depuis les fils des composants qui écoutent le magasin via un tube asynchrone
  • déclenchement manuel de la détection de changement lorsque vous déclenchez les événements susmentionnés
  • déclencher ces événements à un autre moment ou dans un setTimeout

J'utilise ngrx et j'ai une situation comme celle-ci:

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

Bien sûr, dans cette situation, j'obtiens l'erreur, mais lorsque j'ajoute un drapeau booléen dans un composant comme celui-ci:
app.component.html -> <div *ngIf='show'>test</div>
app.component.ts ->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

L'erreur est partie, bien sûr, je comprends pourquoi, mais je veux connaître vos opinions, est-ce une bonne solution? Quels sont les avantages et les inconvénients ??

Je n'utilise pas ngrx mais j'ai le problème avec une simple vérification avec querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);

La solution de @bogdancar avec ChangeDetectorRef.detectChanges() supprime l'erreur mais ce serait bien de ne pas avoir à importer ChangeDetectorRef en corrigeant ce qui cause l'erreur en premier lieu.

J'ai eu le même problème lorsque j'utilise rxjs dans angular 4.4.4. Je viens de souscrire un objet observable fourni par le service DI. Mon travail ci-dessous:

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

J'utilise un setTimeout pour contourner le problème. Maintenant ça marche, mais c'est délicat. Je ne me sens pas bien.

ngOnChanges (changements: SimpleChanges | any) fait l'affaire

Le problème a été résolu simplement après avoir déplacé la logique de modification dynamique des paramètres dans la méthode ngDoCheck ().
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61 Je pense que c'est une très mauvaise idée. ngDoCheck est beaucoup appelé par Angular et si votre sourceArr a beaucoup de données, cela pourrait nuire à vos performances.

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

Je recommanderais plutôt pour l'instant d'utiliser ngAfterViewInit avec setTimeout :

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

exemple:

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

@dgroh vous n'avez pas besoin d'ajouter un délai d'expiration réel, vous voulez juste que js exécute votre code dans une prochaine tâche asynchrone, ce qui signifie une autre exécution de détection de changement.

@victornoel Je crois que cela fonctionnerait avec les observables à moins que vous n'appeliez explicitement detectChanges de ChangeDetectorRef . Je souhaite que tout cela ne soit pas nécessaire.

@dgroh vous devez m'avoir mal compris, je préconisais juste d'écrire:

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

N'est-il pas possible d'ajouter une étape après ngAfterViewInit comme un setTimeout ? (par exemple: ngAfterViewLoad )

@ aks4it , cela semble fonctionner, mais j'aimerais plus de détails sur la façon dont cela fonctionne? Le code de génération de composant dynamique est-il synchrone?

La démo officielle de plunker de Angular Docs a la même erreur. Pourriez-vous y remédier? :) Merci

@luckylooke

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

cela marche?

Je ne comprends pas votre exigence mais vous voulez vraiment un intervalle de 3 secondes? si oui cela fonctionne, et même si vous voulez appeler directement loadComponent je succède avec:

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

@istiti J'ai pu résoudre le problème par ChangeDetectorRef solution ChangeDetectorRef , en mettant detectChanges() après ... data = data

Mais comme il s'agit d'un extrait de la documentation officielle, j'étais curieux de connaître la solution officielle du problème. Pas de timeout ou ChangeDetectorRef.

Désolé j'ai été trop bref dans le premier post .. Je voulais souligner la gravité du problème, car même les documents angulaires ont la même chose dans le code.

Juste pour info, je ne suis pas sûr de ce qui cause vraiment ici, mais les erreurs ont disparu lorsque j'ai activé le mode prod dans main.ts
`enableProdMode ()

// if (environnement.production) {

// enableProdMode ();

//} `

"@ angular / core": "^ 4.2.4"
"dactylographié": "~ 2.3.3"

@bakthaa ... c'est un comportement supposé car le contrôle de contrôle (si les valeurs des composants @Inputs() sont les mêmes) qui est appelé après chaque cycle de CD est invoqué uniquement en mode développement et non en mode production de l'application Angular. Le problème est donc caché en mode production mais il est toujours là ... et peut probablement être une source de différents effets secondaires indésirables ou trompeurs en mode production.

J'utilise Angular 5, j'ai pu résoudre ce problème en injectant le ChangeDetectorRef dans le composant et en le forçant à détecter les changements.

    constructor(private changeDetector: ChangeDetectorRef) {}

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

@ Sabri-Bouchlema ... oui, c'est un moyen ... mais vous devriez quand même repenser une fois de plus ... pourquoi un composant @Inputs est changé pendant un cycle de CD ... et décider si le la logique est correcte ... et il est vrai que dans 95% des cas le code est possible de changer pour éviter le problème sans appeler de detectChanges() supplémentaire.

Utilisation angulaire 5.1.2. J'obtiens cette erreur lors de l'ouverture d'une boîte de dialogue de matériau dans ngAfterViewChecked. Comme @ aks4it l'a signalé, j'ai dû envelopper le code dans un setTimeout pour le faire fonctionner.

Je suis confronté au même problème avec la version 4.01
ng-version = "4.0.1

Idem ici avec angulaire 4.4.6

Correction en utilisant le this.changeDetector.detectChanges();

J'ai écrit ce plunker en utilisant des variables de modèle et je ne comprends pas pourquoi j'ai ExpressionChangedAfterItHasBeenCheckedError. Si je change l'ordre de mes composants, l'erreur disparaît:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

J'ai essayé changeDetector.detectChanges mais ne fonctionne pas

@AlexCenteno ... parce que la logique d'évaluation va de haut en bas ... et value propriété de comp1 est d'abord définie @Input() public value:String=null; puis réinitialisée en utilisant la référence de comp2.value ... <div comp1 [value]="comp2.value"></div> qui est mis entre temps à Hello via son propre @Input() ... le tout avec un cycle CD ... si les entrées sur les composants enfants sont les mêmes qu'au début du cycle CD en mode dev détecte le changement value propriété comp1 .

salut @ mlc-mlapis merci pour votre temps. Il n'y a donc aucun moyen d'éviter l'erreur? Je suppose que c'est quelque chose dont je ne devrais pas m'inquiéter une fois que j'appelle enableProdMode?

@AlexCenteno ... oui, en mode prod, la vérification après le cycle du CD n'est pas invoquée ... mais cela ne signifie pas que vous devriez être satisfait de cela. Vous devez appliquer une logique différente pour transmettre les composants enfants croisés value . Il y a aussi un problème que <div> n'a pas de propriété value . Pourquoi utilisez-vous un composant attributaire? Pourquoi pas simplement:

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

car alors utiliser @Output() sur <comp2> serait bien et cela vous permettrait de mettre à jour [value] sur comp1 sans aucune erreur.

Utiliser [email protected] avec angular@5 standart store.select lancer un avertissement ExpressionChangedAfterItHasBeenCheckedError en mode dev et fonctionner incorrectement en mode prod. La répartition et la sélection se font à différents niveaux / conteneurs.

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

Cette approche supprime le message d'avertissement, mais cela semble être plus compliqué:

@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();
    });
  }
}

Y a-t-il une idée comment résoudre ce problème plus correctement?

@rimlin avez-vous essayé de vous abonner directement à la boutique?

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

je pense que j'ai eu un problème similaire. et en évitant async dans le modèle a résolu le problème.

@ piq9117 merci pour les conseils, essayé votre solution, il semble être plus clair avec moins de code, mais ici aussi besoin d'appeler this.cd.detectChanges(); , sinon la vue ne sera pas mise à jour.

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

J'utilise Angular 5.2, j'ai fait quelque chose comme ça. Travaille pour moi

constructor(private changeDetector: ChangeDetectorRef) {}

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

Utilisation d'Angular 5.2, corrigé à l'aide de detectChanges ()

Oui, nous avons également corrigé cela en utilisant la route detectChanges () (Angular 5.1). Je suis avec beaucoup d'autres ici qui pensent que ce n'est encore qu'une solution de contournement et ne devrait pas être traité comme la réponse à long terme. Il devrait y avoir un autre hook de cycle de vie à utiliser.

Déclencher un cycle de détection des changements entièrement nouveau pour permettre des changements comme celui-ci semble tout simplement sous-optimal.

Cela semble continuer encore et encore, même si toutes les réponses et tous les conseils ont déjà été publiés ci-dessus.

  1. La bonne façon de résoudre ce problème consiste à réorganiser / restructurer vos composants et la gestion des données pour qu'ils fonctionnent avec une seule passe de détection de changement de haut en bas. Cela signifie qu'aucun composant enfant ne peut modifier une valeur utilisée dans le modèle d'un composant parent. Réévaluez ce que vous avez construit et vérifiez que les données ne circulent que dans une seule direction, dans l'arborescence des composants. Cela peut être aussi simple que de déplacer votre code de ngAfterViewInit à ngOnInit dans certains cas.
  2. this.changeDetectorRef.detectChanges(); est le moyen officiel et reconnu de gérer cela dans moins de 5% des cas où la première étape pose problème.

Autres notes:

  1. Soyez très prudent lorsque vous faites quoi que ce soit dans ngAfterViewInit car la modification des valeurs utilisées dans le DOM peut provoquer cette erreur.
  2. Soyez très prudent lorsque vous utilisez l'un des hooks de cycle Checked vie Check ou Checked car ils peuvent être appelés à chaque cycle de détection de changement.
  3. setTimeout peut contourner ce problème, mais les deux solutions ci-dessus sont préférées.
  4. Ignorer cette erreur en mode de développement et être heureux que le mode de production "corrige" l'erreur n'est pas valide comme expliqué ci-dessus. Ces vérifications ne sont pas effectuées en mode prod, mais votre code de mode prod laisse toujours les modifications non détectées et entraîne une désynchronisation de la vue et des données. Cela peut être inoffensif dans certains cas, mais peut entraîner une corruption des données / vues et des problèmes plus graves si elle est ignorée.
  5. Le flux de données à sens unique n'a pas vraiment beaucoup changé dans Angular, donc publier que vous l'avez atteint dans la version X d'Angular n'est pas très utile.
  6. L'ajout de hooks ou d'états de cycle de vie supplémentaires n'est pas une solution à cela. Les développeurs ajouteraient simplement du code qui viole le flux de données à sens unique dans ces hooks et déclenchent cette même erreur.

Enfin, rappelez-vous simplement que cette erreur n'indique pas un problème avec Angular. Cette erreur est là pour vous faire savoir que vous avez fait quelque chose de mal dans votre code et que vous violez le système de flux de données à sens unique qui rend Angular rapide et efficace.

Les articles ci-dessus et de nombreux articles de blog approfondissent ce sujet et méritent d'être lus, car vous devez comprendre ce modèle de flux de données à sens unique et comment créer des composants et y transmettre des données.

@Splaktar Serait-il préférable de verrouiller ce problème (et de me supprimer) pour rendre le commentaire ci-dessus exceptionnel? Le fil est déjà trop long et la plupart d'entre eux ne font que se répéter. 😃

@Splaktar Vous faites ce point "Cela signifie qu'aucun composant enfant ne peut changer une valeur qui est utilisée dans le modèle d'un composant parent".

Supposons que vous ayez un composant conteneur qui inclut la navigation, les titres de page, etc., et qu'il charge une collection de sous-composants, chacun pouvant charger ses propres sous-composants. Les composants chargés dans le conteneur changeront en fonction des données et les sous-composants qu'ils chargent changeront en fonction des données. Chacun de ces sous-composants doit être en mesure de communiquer au conteneur parent des informations sur ce qu'ils sont afin que le conteneur parent puisse afficher le Nav de niveau supérieur approprié à l'utilisateur peut-être (piste de navigation peut-être, ou quelque chose de similaire), ainsi en tant qu'informations pouvant être utilisées dans les titres pour identifier au niveau du conteneur ce qui est chargé en vue.

Dans cette architecture, qui est une architecture extrêmement courante dans mon expérience, je ferais usage d'un service d'état commun avec lequel les sous-composants s'enregistreraient eux-mêmes, et le composant parent souscrit afin que les enfants puissent éduquer le parent et le parent. peut afficher les informations appropriées.

Cela ne suit pas une approche ascendante, car le parent obtiendrait toutes les données pertinentes du service et transmettrait toutes les informations requises par les composants enfants via une communication unidirectionnelle.

Cela semble aller à l'encontre de ce que vous suggérez ci-dessus, et pour moi, c'est la source du problème. Lorsque le composant enfant s'inscrit auprès du service, le composant parent renvoie l'erreur.

Est-ce incorrect?

Et n'oubliez jamais qu'il existe un bogue approuvé:
https://github.com/angular/angular/issues/15634

@ Martin-Wegner Veuillez consulter https://github.com/angular/angular/pull/18352#issuecomment -354170352

@alignsoft ... avez-vous une simple reproduction de votre coque et du problème sous forme de Stackblitz? Parce qu'il semble que quelque chose d'autre devrait affecter le cas que la logique que vous avez décrite.

@trotyl les solutions de @Splaktar ne sont pas liées à mon problème simple ici https://github.com/angular/angular/issues/17572#issuecomment -311589290 ou?

@ Martin-Wegner ... dans votre cas, il suffit de changer ngAfterViewInit en ngOnInit hook.

@alignsoft , comme @ mlc-mlapis l'a dit, un exemple de Stackblitz serait très utile pour démontrer l'architecture appropriée pour ce cas. Ceci est tout à fait possible en utilisant le bon ensemble de services, d'observables, de hooks de cycle de vie, etc. Ce cas spécifique serait également un bon sujet pour StackOverflow.

@Splaktar Une fois que ma date limite actuelle est

@ mlc-mlapis wow merci, ça marche :)

@alignsoft , aujourd'hui, je stackblitz . Si vous cliquez sur "CMS System" dans la ExpressionChangedAfterItHasBeenCheckedError navigation de gauche, l'exception

Dans l'exemple, l'admin.component agit comme un composant parent chargé de 3 choses:

  • affichage d'un nav gauche
  • un ticker supérieur que les sous-composants peuvent utiliser pour afficher leur propre message ticker
  • une section de contenu principale qui affiche des données spécifiques au sous-composant via <router-outlet></router-outlet> .

Composant parent

<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();
  }
}

En recherchant comment communiquer des données de sauvegarde au composant parent lors de l'utilisation de <router-outlet></router-outlet> , j'ai lu que si j'utilise @Output pour renvoyer des données au composant parent, la directive router-outlet se terminera gérer l'événement. Puisqu'il s'agit d'un événement personnalisé, la directive router-outlet ne saura pas comment le gérer. J'ai donc sauté cette méthode. Une fois que j'ai lu sur les services partagés sur angular.io, j'ai écrit le mien:

Service partagé

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);
  }
}

Une fois l'un des composants enfants appelé this.adminService.announceTickedMessage(this._tickedMessage); :

Composant enfant

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);
  }
}

le composant parent a lancé une exception ExpressionChangedAfterItHasBeenCheckedError car il a essayé de mettre à jour l'une de ses propriétés publiques avec la valeur de message cochée envoyée par le composant enfant:

Composant parent (lève une exception)

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();
  }
}

Ce qui a corrigé le ExpressionChangedAfterItHasBeenCheckedError était en exécutant le rappel d'abonnement de manière asynchrone et en attendant le message coché:

Composant parent (avec attente asynchrone)

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();
  }
}

Venant du monde WPF, il semble que nous devions dire au thread d'interface utilisateur d'attendre le rappel d'abonnement observable puisque les observables sont exécutés de manière asynchrone?

Je ne fais que commencer avec Angular, alors s'il vous plaît, ayez de la compassion envers mes compétences Angularz: P

@ktabarez Merci, cela m'a beaucoup aidé!

@ktabarez Merci! Cela semble bien fonctionner! J'emballais l'abonnement dans le composant parent dans un délai d'expiration, qui fonctionnait également, mais je me sentais sale.

Je vois que lors de l'interrogation de l'état redux, il est garanti de livrer, mais au démarrage comme ça lorsqu'il est mis dans ngOnInit par opposition aux constructeurs

@ktabarez Merci beaucoup!

Votre solution async ... await in subscribe fonctionne.

@ktabarez Votre solution fait essentiellement la même chose qu'un setTimeout ({}, 0) mais elle semble plus élégante. Tout à fait comme ça! Cela m'a également fait réaliser que tout peut être attendu (un peu comme C # attend Task.FromResult (tickedMessage)), alors bravo pour cela!

Alors, question: pourquoi devrions-nous faire un async / wait sur un calcul d'une propriété sur une observable qui obtient des données du back-end qui pourraient changer fréquemment? Qu'en est-il de ngClass rend cela nécessaire? J'obtiens ceci en utilisant un {observable | async} pipe. Donc, async / await n'est pas la meilleure approche pour moi. (ou le tube asynchrone n'est pas la meilleure approche)

J'ai ce problème où mon composant parent a plusieurs composants enfants. L'un des composants enfants a une entrée dans laquelle il a besoin d'une donnée de la propriété du composant parent et la propriété obtient ses données à partir d'un observable / promesse (essayé les deux).

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);
}

Je suis tombé sur toutes les solutions trouvées dans le fil. Mais rien ne fonctionne dans mon cas. Heureusement, certains l'ont laissé entendre que c'est un problème dû à la manipulation d'un DOM, comme mettre une expression dans _ (ngIf) _ comme exemple.

Donc, pour éviter cela, j'essaye d'expérimenter avec ng-template , ng-container et 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>

J'ai pu résoudre ce problème sans utiliser ChangeDetectorRef ou setTimeout()

Pour moi, cette erreur s'est produite lors de l'affichage / du masquage du champ en fonction de la condition, j'ai pu la résoudre après avoir appliqué les valeurs du paramètre des champs:

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

J'ai essayé de le faire avec une boîte de dialogue mat qui s'ouvre sur init, et j'ai littéralement essayé toutes les solutions que j'ai vues sur ce thread (définir le délai d'expiration, la résolution de la promesse, le tube observable, après la vue init, après le contenu init) et leurs combinaisons. Ça ne marche toujours pas. Ensuite, j'ai vu @Splaktar poster sur la façon dont cela viole le flux de données à sens unique, alors j'ai littéralement créé un drapeau dans ngrx disant d'ouvrir la boîte de dialogue et m'y suis abonné, ce qui est faux au départ et j'obtiens toujours cette erreur. Que diable dois-je faire? N'y a-t-il littéralement aucun moyen d'ouvrir une boîte de dialogue modale sans l'attacher à un événement complètement séparé?

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

Fonctions auxiliaires:
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)); }

Utilisation angulaire 6.0.0-rc.1

J'utilise angular avec ngrx et j'ai cette erreur après avoir soumis un formulaire.
fixé par réglage
ChangeDetectionStrategy.OnPush
sur le composant de formulaire

ayant toujours le problème. essayé toutes sortes de solutions de contournement. setTimeout fonctionne la première fois, mais le déclenchement ultérieur de dialog.open entraîne la même erreur. Un statut à ce sujet?

Je suis confronté au même problème. J'ai tout essayé, mais il semble que ce problème se pose sur un flux de données à sens unique.
Ici, j'essaye de mettre à jour selectedwoids. où le problème résout mais la même erreur se produit.

        <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>

private setSelectedWoidsCount () {
if (this.isSelectAllWoids) {
this.selectedWoids = this.allWoids;
}
}

Parce que vous cassez le pipeline de rendu d'Angular ...

Je vois des erreurs "ExpressionChanged ..." similaires après le passage de 4.1.2 à 4.2.3 .

Dans mon cas, j'ai deux composants qui interagissent via un service. Le service est fourni via mon module principal, et ComponentA est créé avant ComponentB .

Dans 4.1.2 , la syntaxe suivante fonctionnait très bien sans erreur:

Modèle _ComponentA: _

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

Classe _ComponentB: _

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

Dans 4.2.3 , je dois modifier le modèle de ComponentA comme suit pour éviter l'erreur "ExpressionChanged ...":

Modèle _ComponentA: _

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

J'essaie toujours de comprendre pourquoi cela ne fait que devenir un problème et pourquoi le passage de *ngIf à [hidden] fonctionne.

Avez-vous trouvé comment cela fonctionnait lors du passage de ngIf à [hidden]

Existe-t-il encore une solution à ce problème dans Angular 6?

@alokkarma ... trop vieux ... vous devriez le migrer vers Angular 6 ou 5 au moins. Mais votre exemple de code semble clair ... vous modifiez la propriété de service dans le composant enfant B, et cela affecte certains comportements dans le composant enfant A. Les deux composants enfants sont placés dans le même parent, donc tous les trois sont traités ensemble dans relation dans un cycle de CD. Qu'attendez-vous?

Le fait que le même cas fonctionnait dans la version 4.1.2 est lié à certains bogues qui ont été éliminés dans les versions ultérieures, y compris 4.2.3.

J'utilise Angular 6. J'utilise un MessageService pour communiquer entre mon composant d'authentification et mon composant d'application afin d'afficher un contenu de barre de navigation différent si l'utilisateur est connecté (ou non)

Cela fonctionne bien en dehors de cette erreur!
J'ai essayé d'autres solutions telles que EventEmitter mais cela ne fonctionne pas! ou j'obtiens la même erreur.

@dotNetAthlete ... montre une démo de reproduction simple sur Stackblitz. En parler sans le vrai code est difficile.

@dotNetAthlete J'utilise le même mécanisme de base où j'ai un service d'état qui contient un titre de page en tant que sujet de comportement (asObservable) dans le composant principal du conteneur, et des composants enfants chargés dedans qui définissent le titre de page approprié dans le service d'état pour le conteneur parent à observer et à afficher.

Il y avait toujours une erreur `` Expression modifiée '' lorsque le conteneur initialisait la valeur, puis le composant enfant la mettait immédiatement à jour, donc je le fais maintenant dans le parent / conteneur:

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

Là où le service d'État a ceci:

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

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

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

Cela élimine le problème. Cette solution est basée sur des commentaires utiles d'autres personnes dans ce fil.

@alignsoft - belle solution - problème résolu dans l'application Angular 6.

Je vois ceci sur la page recharger sur angular 7 maintenant ...
Je suis en train de définir une valeur de composants enfants sur les parents ngAfterViewInit
J'ai dû faire cela sur le composant enfant

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

J'ai trouvé la solution dans ce fil de discussion https://github.com/angular/angular/issues/10762
Après avoir utilisé le hook de cycle de vie AfterContentInit , l'erreur disparaît.

@alignsoft , juste en ajoutant à votre changement, puisque nous utilisons un BehavioralSubject au lieu d'un Subject,

Je l'ai raffiné comme suit:

\\ 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);

Mais je suis d'accord avec @daddyschmack , je voudrais obtenir toute ressource qui pointe vers des informations concernant l'un des correctifs mentionnés ci-dessus ( cdr.detectionChanges() ou async await ou aftercontentchecked ), pourquoi [hidden] attribut *ngIf

@acidghost Merci pour ce lien, il a résolu mon problème. J'avais en fait renoncé à essayer de le réparer dans une zone de mon application, mais quand cela a recommencé à se reproduire dans une nouvelle zone ...

Ce problème a été automatiquement verrouillé en raison de l'inactivité.
Veuillez signaler un nouveau problème si vous rencontrez un problème similaire ou connexe.

En savoir plus sur notre politique de verrouillage automatique des conversations .

_Cette action a été effectuée automatiquement par un bot._

Cette page vous a été utile?
0 / 5 - 0 notes