web-dev-qa-db-fra.com

Angular2 ngFor OnPush Detection Change avec des mutations de tableau

J'ai un projet de composant de table de données ( angular2-data-table ) dans lequel nous avons modifié le projet de détection de changement traditionnel d'Angular en OnPush pour optimiser les vitesses de rendu.

Une fois que la nouvelle stratégie de détection des modifications a été mise en œuvre, un bogue faisant référence au tableau ne se met pas à jour lorsque l'objet de données est muté, tel que les mises à jour des propriétés de l'objet. Référence: https://github.com/swimlane/angular2-data-table/ issues/255 . Ce type de besoin peut être optimisé pour des tâches telles que l'édition en ligne ou la modification de données externes en une seule propriété dans une collection de données volumineuse telle qu'un ticker boursier.

Dans un effort pour résoudre le problème, nous avons ajouté un vérificateur de propriété trackBy personnalisé appelé trackByProp. Référence: commit . Malheureusement, cette solution n'a pas résolu le problème.

Sur la page demo sous live reloading, vous pouvez voir que la démo référencée dans la validation ci-dessus est en cours d'exécution mais ne met pas à jour le tableau tant que vous n'avez pas cliqué, ce qui a déclenché la détection des modifications.

La structure du composant ressemble à ceci:

Table > Body > Row Group > Row > Cell

tous ces composants implémententOnPush. J'utilise des sélecteurs/régleurs dans le sélecteur de ligne pour déclencher des recalculs de page comme indiqué ici .

Nous aimerions rester avec la détection OnPushchange pour ceux qui implémentent ce modèle. Toutefois, en tant que projet open source avec plusieurs consommateurs, on pourrait proposer une sorte de fonction de vérification personnalisée pour les valeurs de ligne visibles à l'écran.

Cela dit, trackBy ne déclenche pas la détection des modifications dans les valeurs des cellules de ligne, quel est le meilleur moyen d'y parvenir?

13
amcdnl

La détection de changement angulaire2 ne vérifie pas le contenu des tableaux ou des objets.

Une solution de contournement consiste à créer une copie du tableau après la mutation.

this.myArray.Push(newItem);
this.myArray = this.myArray.slice();

De cette façon, this.myArray fait référence à une instance de tableau différente et Angular reconnaîtra le changement.

Une autre approche consiste à utiliser IterableDiffer (pour les tableaux) ou KeyValueDiffer (pour les objets)

// inject a differ implementation 
constructor(differs: KeyValueDiffers) {
  // store the initial value to compare with
  this.differ = differs.find({}).create(null);
}

@Input() data: any;

ngDoCheck() {
  var changes = this.differ.diff(this.data); // check for changes
  if (changes && this.initialized) {
    // do something if changes were found
  }
}

Voir aussi https://github.com/angular/angular/blob/14ee75924b6ae770115f7f260d720efa8bfb576a/modules/%40angular/common/src/directives/ng_class.ts#L122 _

19
Günter Zöchbauer

Vous pouvez utiliser la méthode markForCheck à partir de ChangeDetectorRef.


J'ai un problème similaire, car j'ai un composant qui contient beaucoup de données et les revérifier tous à chaque cycle de détection de changement n'est pas une option. Mais lorsque nous observons certaines propriétés de URL et que nous modifions les éléments de la vue en conséquence, avec onPush, notre vue n'est pas actualisée (automatiquement).

Donc dans votre constructeur, utilisez DI pour obtenir une instance de changeDetectorRef: constructor(private changeDetectorRef: ChangeDetectorRef)

Et partout où vous devez déclencher un changementDetection: this.changeDetectorRef.markForCheck();

3
maxime1992

Moi aussi, devant le même problème d'optimisation des performances de mon application, je devais utiliser la stratégie changeDetection.OnPush. Je l'ai donc injectée à la fois dans mon composant parent et dans le constructeur de mon composant enfant, l'instance de changeDetectorRef

    export class Parentcomponent{
       prop1;

       constructor(private _cd : ChangeDetectorRef){
          }
       makeXHRCall(){
        prop1 = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

De même dans le composant enfant, l'instance de changeDetectorRef a été injectée

    export class ChildComponent{
     @Input myData: myData[];
     constructor(private _cd : ChangeDetectorRef){
          }
       changeInputVal(){
        this.myData = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

La détection de changement angulaire est déclenchée sur chaque fonction asynchrone: -

  • tout événement DOM comme clic, soumettre, passer la souris.
  • tout appel XHR
  • des timers comme setTimeout (), etc.

Donc, ce genre de ralentit l'application, car même lorsque nous déplaçons la souris, angular déclenche changeDetection. Pour une application complexe couvrant plusieurs composants, cela peut constituer un goulot d'étranglement majeur en termes de performances, car angular utilise ce type d'arborescence comme stratégie de détection de changement de parent à enfant . détection où nous voyons qu'il y a un changement de référence.

Deuxièmement, dans la stratégie OnPush, vous devez faire très attention à ce qu’il ne déclenche le changement que s’il ya un changement de référence d’objet et pas seulement la valeur de la propriété de l’objet, c’est-à-dire que dans Angular change »signifie« nouvelle référence ». 

Pour par exemple: -

    obj = { a:'value1', b:'value2'}'
    obj.a = value3;

La valeur de la propriété 'a' dans 'obj' peut avoir changé mais obj pointe toujours sur la même référence, de sorte que la détection de changement angulaire ne se déclenchera pas ici (sauf si vous le forcez à); Pour créer une nouvelle référence, vous devez cloner l'objet dans un autre objet et attribuer ses propriétés en conséquence.

pour plus de compréhension, lisez à propos des structures de données immmuables, changez la détection ici

2
Gaurav Pandvia

Réponse tardive mais une autre solution consiste à utiliser l'opérateur d'étalement après mutation ..myArr = [...myArr] ou myObj = {...myObj}

Cela peut même être fait en mutant: myArr = myMutatingArr([...myArr]) puisque le paramètre est pris comme nouvelle référence d'un tableau, ce qui fait que la variable prend une nouvelle référence, et donc le contrôle angulaire est appelé.

Comme mentionné, si vous changez la référence, le contrôle sera effectué, l'opérateur d'étalement peut être utilisé dans tous les cas pour faire exactement cela. 

Méfiez-vous cependant que les structures de données imbriquées à l'intérieur des structures de données nécessitent le changement de référence jusqu'au niveau imbriqué. Vous devez faire une itération qui retourne une propagation dans une propagation, comme suit:

myObj = {...myObj, propToChange: { ...myObj.propToChange, nestedPropArr: [ ...myObj.propToChange.nestedPropArr ] } }

ce qui peut devenir compliqué si vous avez besoin d'itération sur des objets et autres. J'espère que cela aide quelqu'un!

0