web-dev-qa-db-fra.com

Comment gérer Angular2 "L’expression" a changé après avoir été cochée, exception lorsqu’une propriété de composant dépend de la date et de l’heure actuelle

Mon composant a des styles qui dépendent de la date/heure actuelle. Dans ma composante, j'ai la fonction suivante.

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmp est calculé à partir de la date/heure actuelle. La couleur change si dtoDate est dans les dernières 24 heures.

L'erreur exacte est la suivante:

L'expression a changé après avoir été vérifiée. Valeur précédente: 'hsl (123, 80%, 49%)'. Valeur actuelle: 'hsl (123, 80%, 48%)'

Je sais que l'exception n'apparaît en mode de développement qu'au moment où la valeur est vérifiée. Si la valeur cochée est différente de la valeur mise à jour, l'exception est levée.

J'ai donc essayé de mettre à jour la date et l'heure en cours à chaque cycle de vie à l'aide de la méthode de raccordement suivante pour éviter l'exception:

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

... mais sans succès.

87
Anthony Brenelière

Exécuter la détection de changement explicitement après le changement:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}
225
Günter Zöchbauer

Ceci est un bon article pour comprendre cette erreur. Ce n'est pas trop long à lire.

32
tomonari_t

Comme mentionné par @leocaseiro sur question github .

J'ai trouvé 3 solutions pour ceux qui recherchent des solutions faciles.

1) Passage de ngAfterViewInit à ngAfterContentInit

2) Passage à ngAfterViewChecked combiné avec ChangeDetectorRef en tant que suggéré sur # 14748 (commentaire)

3) Conservez ngOnInit () mais appelez ChangeDetectorRef.detectChanges() après vos changements.

15
candidJ

Petite solution de contournement pour ce problème:

ngAfterViewInit() { // or ngOnInit or whatever
    setTimeout(() => {
        this.dateNow = new Date();
    });
}
12
Sergei Panfilov

Je pense la solution la meilleure et la plus propre vous pouvez l’imaginer est la suivante:

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Testé avec Angular 5.2.9

3
JavierFuentes

Dans notre cas, nous FIXONS en ajoutant changeDetection au composant et appelons detectChanges () dans ngAfterContentChecked, codez comme suit

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}
2

Un petit travail que j'ai utilisé plusieurs fois

Promise.resolve(null).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

Je remplace principalement "null" par une variable que j'utilise dans le contrôleur.

0
Khateeb321

Voici un petit extrait de tomonari_tréponse sur les causes de cette erreur, j'ai essayé d'inclure uniquement les parties qui m'ont aidé à comprendre cela.

L'article complet montre des exemples de code réels sur chaque point présenté ici.

La cause fondamentale est celle du cycle de vie angulaire:

Après chaque opération, Angular se souvient des valeurs qu’elle utilisait auparavant une opération. Ils sont stockés dans la propriété oldValues ​​du vue des composants.

Une fois les vérifications terminées pour tous les composants, Angular démarre puis le cycle de digestion suivant, mais au lieu d’effectuer des opérations, il compare les valeurs actuelles à celles dont il se souvient. le cycle de digestion précédent.

Les opérations suivantes sont celles en cours de vérification lors du cycle de digestion:

vérifiez que les valeurs transmises aux composants enfants sont identiques à celles de les valeurs qui seraient utilisées pour mettre à jour les propriétés de ces composants à présent.

vérifiez que les valeurs utilisées pour mettre à jour les éléments DOM sont identiques à celles de les valeurs qui seraient utilisées pour mettre à jour ces éléments exécutent maintenant le même.

vérifie tous les composants enfants

Et ainsi, l'erreur est renvoyée lorsque les valeurs comparées sont différentes., blogueur Max Koretskyi a déclaré:

Le coupable est toujours le composant enfant ou une directive.

Et enfin, voici quelques exemples du monde réel qui provoquent généralement cette erreur:

  • Services partagés 
  • Diffusion d'événement synchrone 
  • Instanciation du composant dynamique

Chaque échantillon peut être trouvé ici (plunkr), dans mon cas, le problème était une instanciation de composant dynamique. 

De plus, de par ma propre expérience, je recommande vivement à tous d’éviter la solution setTimeout. Dans mon cas, cela a provoqué une boucle "presque" infinie (21 appels que je ne suis pas disposé à vous montrer comment les provoquer), 

Je recommande de toujours garder à l'esprit le cycle de vie angulaire afin que vous puissiez prendre en compte la manière dont ils seraient affectés chaque fois que vous modifiez la valeur d'un autre composant. Avec cette erreur, Angular vous dit:

Vous faites peut-être cela dans le mauvais sens, êtes-vous sûr d'avoir raison?

Le même blog dit aussi:

Souvent, le correctif consiste à utiliser le bon crochet de détection de changement pour créer un composant dynamique.

0
Luis Limas