146 votes

ExpressionChangedAfterItHasBeenCheckedError : L'expression a changé après avoir été vérifiée. Valeur précédente : 'undefined'.

Je sais qu'il y a beaucoup de questions identiques déjà postées dans stack-overflow et j'ai essayé différentes solutions pour éviter l'erreur d'exécution mais aucune d'entre elles ne fonctionne pour moi.

Error

Composant et code Html

export class TestComponent implements OnInit, AfterContentChecked {
    @Input() DataContext: any;
    @Input() Position: any;
    sampleViewModel: ISampleViewModel = { DataContext: : null, Position: null };
    constructor(private validationService: IValidationService, private modalService: NgbModal, private cdRef: ChangeDetectorRef) {
    }

    ngOnInit() {

    }
    ngAfterContentChecked() {

            debugger;
            this.sampleViewModel.DataContext = this.DataContext;
            this.sampleViewModel.Position = this.Position;

    }

<div class="container-fluid sample-wrapper text-center" [ngClass]="sampleViewModel.DataContext?.Style?.CustomCssClass +' samplewidget-'+ sampleViewModel.Position?.Columns + 'x' + sampleViewModel.Position?.Rows">
     //some other html here
</div>

Remarque : Ce composant est chargé dynamiquement à l'aide du DynamicComponentLoader.

Après mon enquête, j'ai identifié quelques problèmes.

Tout d'abord, ce composant enfant est chargé dynamiquement en utilisant DynamicComponentResolver et en passant les valeurs d'entrée comme ci-dessous

 ngAfterViewInit() {
    this.renderWidgetInsideWidgetContainer();

  }

  renderWidgetInsideWidgetContainer() {
    let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
    let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
    let viewContainerRef = this.widgetHost.viewContainerRef;
    viewContainerRef.clear();
    let componentRef = viewContainerRef.createComponent(componentFactory);
    debugger;
    (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
    (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;

  }

Même si j'ai modifié le html de mon composant enfant comme ci-dessous, j'obtiens la même erreur. il suffit d'ajouter un attribut angular ngclass.

<div class="container-fluid ds-iconwidget-wrapper text-center" [ngClass]="Sample">

</div>

Mes liaisons de données et tout le reste fonctionnent bien, dois-je faire quelque chose sur le composant parent ? J'ai déjà essayé tous les événements du cycle de vie dans le composant enfant.

0 votes

Comment ajouter TestComponent ?

1 votes

@Maximus comme composant d'entrée

0 votes

@Maximus J'ai essayé d'appeler this.cdRef.detectChanges() ; explicitement, mais cela ne fonctionne pas.

168voto

Richie Fredicson Points 596

Vous devez dire angular que vous avez mis à jour le contenu après ngAfterContentChecked vous pouvez importer ChangeDetectorRef de @angular/core et appeler detectChanges

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

constructor( private cdref: ChangeDetectorRef ) {}

ngAfterContentChecked() {

this.sampleViewModel.DataContext = this.DataContext;
this.sampleViewModel.Position = this.Position;
this.cdref.detectChanges();

 }

0 votes

Voir mes commentaires en question J'ai déjà essayé cette solution mais j'obtiens toujours la même erreur

7 votes

J'ai importé AfterContentChecked qui n'a que this.cdref.detectChanges() ; ça marche pour moi merci

0 votes

@MohamedAboElmagd Pouvez-vous nous dire comment vous avez fait pour que cela fonctionne ?

75voto

Maximus Points 1342

Le site ngAfterContentChecked est déclenché lorsque les mises à jour des liens pour les composants/directives enfants sont déjà terminées. Mais vous mettez à jour la propriété qui est utilisée comme une entrée de liaison pour la directive ngClass directive. C'est là que réside le problème. Quand Angular exécute l'étape de validation, il détecte qu'il y a une mise à jour en attente des propriétés et lance l'erreur.

Pour mieux comprendre l'erreur, lisez ces deux articles :

Réfléchissez à la raison pour laquelle vous devez modifier la propriété dans le fichier ngAfterViewInit le crochet du cycle de vie. Tout autre cycle de vie qui est déclenché avant ngAfterViewInit/Checked fonctionnera, par exemple ngOnInit ou ngDoCheck ou ngAfterContentChecked .

Donc pour le réparer, bougez renderWidgetInsideWidgetContainer à la ngOnInit() le crochet du cycle de vie.

0 votes

Pourquoi mettez-vous à jour la propriété dans le ngAfterViewInit/Checked ?

0 votes

En fait, il n'y a pas de mise à jour. Je place mes données d'entrée dans un modèle de vue et finalement toutes les propriétés sont liées à ce modèle comme une classe d'enveloppe. Lorsque je lie directement le contexte de données @Input, j'obtiens la même erreur. Il n'y a pas de solution pour se débarrasser de cette erreur par le biais de l'un des événements du cycle de vie. Laissez-moi essayer ngAfterViewInit/Checked mentionné dans votre réponse.

3 votes

Essayez ngOnInit ou ngDoCheck ou ngAfterContentChecked. Si cela ne fonctionne toujours pas, créez un plunker.

13voto

bresleveloper Points 792

Si vous utilisez <ng-content> avec *ngIf vous allez forcément tomber dans cette boucle.

La seule solution que j'ai trouvée était de changer *ngIf à display:none fonctionnalité

1 votes

Ou vous pouvez utiliser [hidden]="!condition".

6voto

Chetan Laddha Points 451

Deux solutions :

  1. Assurez-vous que si vous avez des variables de liaison, alors déplacez ce code vers settimeout ( { }, 0) ;
  2. Déplacez votre code connexe vers ngAfterViewInit méthode

0 votes

Cela dépend du fait qu'il déplace votre code dans la file d'attente des événements et qu'il sera exécuté lorsque l'exécution en cours sera terminée. JavaScript est un langage de script monofil, il peut donc exécuter un seul morceau de code à la fois (en raison de sa nature monofil) ; chacun de ces blocs de code "bloque" la progression d'autres événements asynchrones. Le code settimeout ne sera exécuté que lorsque votre code actuel aura terminé son exécution.

0 votes

Hmm ceci a réparé le mien. Je suis allé dans le composant et j'ai mis tous les changements de bindings dans un timeout de 0 sec et ça marche. Est-ce une bonne solution ou un hack ? J'appelle la méthode qui change la valeur de la liaison probablement 2 fois pendant le cycle de vie.

2voto

rooby Points 214

J'ai eu le même problème en essayant de faire la même chose que vous et je l'ai résolu avec quelque chose de similaire à la réponse de Richie Fredicson.

Lorsque vous exécutez createComponent(), il est créé avec des variables d'entrée non définies. Ensuite, lorsque vous attribuez des données à ces variables d'entrée, cela change les choses et provoque cette erreur dans votre modèle enfant (dans mon cas, c'était parce que j'utilisais l'entrée dans un ngIf, qui a changé une fois que j'ai attribué les données d'entrée).

Le seul moyen que j'ai trouvé pour l'éviter dans ce cas précis est de forcer la détection des changements après avoir assigné les données, mais je ne l'ai pas fait dans ngAfterContentChecked().

Votre exemple de code est un peu difficile à suivre mais si ma solution fonctionne pour vous, ce serait quelque chose comme ceci (dans le composant parent) :

export class ParentComponent implements AfterViewInit {
  // I'm assuming you have a WidgetDirective.
  @ViewChild(WidgetDirective) widgetHost: WidgetDirective;

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private changeDetector: ChangeDetectorRef
  ) {}

  ngAfterViewInit() {
    renderWidgetInsideWidgetContainer();
  }

  renderWidgetInsideWidgetContainer() {
    let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
    let viewContainerRef = this.widgetHost.viewContainerRef;
    viewContainerRef.clear();
    let componentRef = viewContainerRef.createComponent(componentFactory);
    debugger;
    // This <IDataBind> type you are using here needs to be changed to be the component
    // type you used for the call to resolveComponentFactory() above (if it isn't already).
    // It tells it that this component instance if of that type and then it knows
    // that WidgetDataContext and WidgetPosition are @Inputs for it.
    (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
    (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;
    this.changeDetector.detectChanges();
  }
}

Le mien est presque le même que celui-là, sauf que j'utilise @ViewChildren au lieu de @ViewChild car j'ai plusieurs éléments hôtes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X