web-dev-qa-db-fra.com

Angular 4 ExpressionChangedAfterItHasBeenCheckedError

dans ParentComponent =>

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '[object Object]'.
            at viewDebugError (vendor.bundle.js:8962)
            at expressionChangedAfterItHasBeenCheckedError (vendor.bundle.js:8940)

Composant parent Html

<div>
  <app-child-widget [allItems]="allItems" (notify)="eventCalled($event)"></app-child-widget>
<div>

Composant parent

export class ParentComponent implements OnInit {

  returnedItems: Array<any> = [];
  allItems: Array<any> = [];

  constructor(
  ) { }

  ngOnInit() {
     this.allItems = // load from server...
  }

  eventCalled(items: Array<any>): void {
    this.returnedItems = items;
  }
}

Composante enfant

@Component({
  selector: 'app-child-widget',
  templateUrl: 'child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
  @Output() notify: EventEmitter<any> = new EventEmitter();
  @Input() private allItems: Array<any>;

  constructor() { }

  ngOnInit() {
    doSomething();
  }

  doSomething() {
    this.notify.emit(allItems);
  }
}
18
Saurabh Kumar

L'article Tout ce que vous devez savoir sur l'erreur ExpressionChangedAfterItHasBeenCheckedError explique ce comportement en détail

Cause

Votre problème est très similaire à celui-ci mais au lieu de mettre à jour la propriété parent via un service, vous le mettez à jour via la diffusion d'événements synchrones. Voici la citation du réponse liée :

Au cours du cycle de résumé, Angular effectue certaines opérations sur les directives enfant. L'une de ces opérations consiste à mettre à jour les entrées et à appeler le hook de cycle de vie ngOnInit sur les directives/composants enfants. L'important est que ces opérations soient effectuées dans un ordre strict:

  • Mettre à jour les entrées
  • Appeler ngOnInit

Ainsi, dans votre cas, Angular a mis à jour la liaison d'entrée allItems sur le composant enfant, puis a appelé onInit sur le composant enfant, ce qui a entraîné une mise à jour du composant allItems du composant parent. Vous avez maintenant une incohérence dans les données. Le composant parent a une valeur alors que l'enfant en est une autre. Si Angular continue à synchroniser les modifications, vous obtiendrez une boucle infinie. C'est pourquoi, au cours du prochain cycle de détection de changement, Angular a détecté que allItems avait été modifié et a généré une erreur.

Solution

Il semble que ce soit un défaut de conception d’application lorsque vous mettez à jour details à partir des composants parent et enfant. Si ce n'est pas le cas, vous pouvez résoudre le problème en émettant l'événement de manière asynchrone de la manière suivante:

export class ChildComponent implements OnInit {
  @Output() notify: EventEmitter<any> = new EventEmitter(true);
                                                        ^^^^^^-------------

Mais il faut faire très attention. Si vous utilisez un autre crochet comme ngAfterViewChecked qui est appelé à chaque cycle de résumé, , vous vous retrouverez dans une dépendance cyclique!

29

Dans mon cas, je changeais l'état de mes données - cette réponse vous obligeait à lire l'explication du cycle de digestion par AngularInDepth.com - dans le niveau html, tout ce que j'avais à faire était de changer la façon dont je traitais les données, :

<div>{{event.subjects.pop()}}</div>

dans

<div>{{event.subjects[0]}}</div>

Résumé: au lieu de popping - qui retourne et supprime le dernier élément d'un tableau, changeant ainsi l'état de mes données - j'ai utilisé la méthode habituelle pour obtenir mon données sans changer d'état, empêchant ainsi l'exception .

0
Miko Chu