Je rencontre un problème avec la détection des modifications onPush dans une application Angular.
J'ai créé une application de démonstration qui illustre le problème: https://stackblitz.com/edit/angular-vcebqu
L'application contient un composant parent
et un composant enfant
.
parent
et enfant
utilisent la détection de changement onPush.
parent
et enfant
ont des entrées divisées en getters et setters, avec this.cd.markForCheck ( );
étant utilisé dans les setters.
<app-child *ngFor="let element of item.elements; let index = index; trackBy: trackElementBy" [element]="item.elements[index]" (elementChange)="item.elements[index]=$event"></app-child>
Le composant parent
crée plusieurs composants enfant
en utilisant un Boucle * ngFor
, comme ceci:
private _element: any; @Output() elementChange = new EventEmitter<any>(); @Input() get element() { return this._element; } set element(newVal: any) { if (this._element === newVal) { return; } this._element = newVal; this.cd.markForCheck(); this.elementChange.emit(this._element); }
Le problème est que si les données sont mises à jour dans le composant parent
, les modifications ne sont pas propagées vers le (s) composant (s) enfant
.
Dans l'application de démonstration, cliquez sur le bouton «modifier» et notez que le premier «élément» Le tableau 'elements' ( elements [0] .order
) est mis à jour dans le parent, mais le changement fait n ot afficher dans le premier élément enfant
du composant. Cependant, si la détection de changement OnPush est supprimée du composant enfant
, cela fonctionne correctement.
3 Réponses :
Vous devez ajouter le décorateur @Input ()
à la méthode setter .
get element() { return this._element; } @Input() set element(newVal: any) { this._element = newVal; }
Voici également d'autres choses:
OnPush
ne définit les entrées que lorsqu'elles ont été modifiées. this.cd.markForCheck ()
dans un setter car le composant est déjà sale. Merci cgTag. J'ai essayé vos suggestions ici: stackblitz.com/edit/angular-bc4tc7 , malheureusement ils ne l'ont pas fait travaille pour moi.
Étant donné que l'entrée transmise au composant enfant n'est pas un tableau, IterableDiffers ne fonctionnera pas. KeyValueDiffers peut cependant être utilisé dans ce cas pour surveiller les changements dans l'objet d'entrée, puis le gérer en conséquence ( lien stackblitz ):
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, KeyValueDiffers, KeyValueDiffer, EventEmitter, Output, Input } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], changeDetection: ChangeDetectionStrategy.OnPush }) export class ChildComponent implements OnInit { private _element: any; @Output() elementChange = new EventEmitter<any>(); get element() { return this._element; } @Input() set element(newVal: any) { if (this._element === newVal) { return; } this._element = newVal; this.cd.markForCheck(); this.elementChange.emit(this._element); } private elementDiffer: KeyValueDiffer<string, any>; constructor( private cd: ChangeDetectorRef, private differs: KeyValueDiffers ) { this.elementDiffer = differs.find({}).create(); } ngOnInit() { } ngOnChanges() { // or here } ngDoCheck() { const changes = this.elementDiffer.diff(this.element); if (changes) { this.element = { ...this.element }; } } }
Solution parfaite! Merci beaucoup.
Il suffit d'ajouter une solution alternative au cas où quelqu'un d'autre aurait ce problème. La principale raison pour laquelle ChildComponent ne reflète pas la nouvelle valeur dans son modèle est que seule la propriété 'order' de 'element' est modifiée par rapport au composant parent, de sorte que le parent injecte la même référence d'objet avec une 'commande' modifiée propriété.
La stratégie de détection des modifications OnPush ne «détecte les modifications» que lorsqu'une nouvelle référence d'objet est injectée dans le composant. Donc, pour que ChildComponent (qui a la stratégie de détection de changement OnPush) déclenche la détection de changement, vous devez injecter une nouvelle référence d'objet à la propriété d'entrée "element" au lieu de la même.
Pour voir cela en action , ouvrez https://stackblitz.com/edit/angular-vcebqu et effectuez les modifications suivantes.
sur le fichier
parent.component.ts
, modifiez la méthodeonClick ($ event) {...}
en:
onClick(event){ const random = Math.floor(Math.random() * (10 - 1 + 1)) + 1; this.item.elements[0] = {...this.item.elements[0], order: random}; }
La dernière ligne remplace la référence d'objet à l'intérieur du tableau à l'index 0 par un nouvel objet identique à l'ancien premier élément du tableau, à l'exception de la propriété order. p >