2
votes

Propagation de la détection de changement angulaire OnPush vers un composant enfant dans une boucle ngFor

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.


0 commentaires

3 Réponses :


0
votes

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:

  • Angular ne définira pas de valeurs en double car OnPush ne définit les entrées que lorsqu'elles ont été modifiées.
  • N'appelez pas this.cd.markForCheck () dans un setter car le composant est déjà sale.
  • Vous n'êtes pas obligé de sortir la valeur de l'entrée. Le composant parent sait déjà ce qui a été entré.


1 commentaires

Merci cgTag. J'ai essayé vos suggestions ici: stackblitz.com/edit/angular-bc4tc7 , malheureusement ils ne l'ont pas fait travaille pour moi.



1
votes

É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 };
    }
  }
}


1 commentaires

Solution parfaite! Merci beaucoup.



1
votes

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éthode onClick ($ 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 >


0 commentaires