angulaire 2 détection de changement et ChangeDetectionStrategy.OnPush

j'essaie de comprendre le ChangeDetectionStrategy.OnPush mécanisme.

ce que je retiens de mes lectures est qu'une détection de changement fonctionne en comparant l'ancienne valeur à la nouvelle valeur. Cette comparaison retournera false si la référence de l'objet n'a pas changé.

Cependant, il semble y avoir certains scénarios où cette "règle" est court-circuité. Pourriez-vous expliquer comment ça marche ?

19
demandé sur Ced 2016-09-30 19:01:07

4 réponses

D'accord, puisque cela m'a pris toute une soirée pour comprendre que j'ai fait un CV pour régler tout dans ma tête et cela pourrait aider les futurs lecteurs. Commençons donc par clarifier certaines choses:

Modifications proviennent d'événements

Un composant peut avoir des champs. Ces champs ne changent qu'après une sorte d'événement, et seulement après cela.

nous pouvons définir un événement comme un clic de souris, une requête ajax, setTimeout...

flux de données du haut vers le haut bas

Angulaire de flux de données est une rue à sens unique. Cela signifie que les données ne circulent pas des enfants aux parents. Seulement de parent à enfant par exemple via le @Input balise. La seule façon de rendre un composant supérieur conscient d'un changement chez un enfant est par un événement. Ce qui nous amène à:

détection de changement de déclencheur d'événement

quand un événement se produit le cadre angulaire vérifiez chaque composant de haut en bas pour voir s'ils ont modifié. si l'un d'eux a changé, il met à jour la vue en conséquence.

contrôle angulaire de tous les composants Après qu'un événement a été tiré. Disons que vous avez un événement de clic sur un composant qui est le composant au niveau le plus bas, ce qui signifie qu'il a des parents mais pas d'enfants. Ce clic pourrait déclencher un changement dans un composant parent via un émetteur d'événement, un service, etc.. Angular ne sait pas si les parents vont changer ou non. C'est pourquoi Angular vérifie chaque composant après un événement a été congédié par défaut.

pour voir s'ils ont changé d'angle, utilisez le ChangeDetector classe.

Détecteur De Changement

chaque composant est doté d'un détecteur de changement de classe. Il est utilisé pour vérifier si un composant a changé d'état après un événement et pour voir si la vue doit être mise à jour. Quand un événement se produit (clic de souris, etc) ce processus de détection de changement se produit pour tous les composants-par défaut-.

Par exemple, si nous avons un ParentComponent:

@Component({
  selector: 'comp-parent',
  template:'<comp-child [name]="name"></comp-child>'
})
class ParentComponent{
  name:string;
} 

nous aurons un détecteur de changement relié au ParentComponent qui ressemble à ceci:

class ParentComponentChangeDetector{
    oldName:string; // saves the old state of the component.

    isChanged(newName){
      if(this.oldName !== newName)
          return true;
      else
          return false;
    }
}

changer les propriétés des objets

comme vous l'avez peut-être remarqué, la méthode isChanged retournera false si vous changez une propriété d'objet. En effet

let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true

Depuis quand une propriété d'objet peut changer sans retourner true dans le changeDetectorisChanged(), l'angle suppose que chaque composante inférieure a pu changer aussi bien. Il vérifiera donc simplement la détection de changement dans tous les composants.

Exemple: ici, nous avons un composant avec un sous-composant. Alors que la détection de changement retournera false pour la composante parent, la vue de l'enfant devrait très bien être mise à jour.

@Component({
  selector: 'parent-comp',
  template: `
    <div class="orange" (click)="person.name='frank'">
      <sub-comp [person]="person"></sub-comp>
    </div>
  `
})
export class ParentComponent {
  person:Person = { name: "thierry" };     
}

// sub component
@Component({
  selector: 'sub-comp',
  template: `
    <div>
      {{person.name}}
    </div>
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

C'est pourquoi le comportement par défaut est de vérifier tous les composants. Parce que même si un sous-composant ne peut pas changer si son entrée n'a pas changé, angular ne sait pas avec certitude que son entrée n'a pas changé vraiment changé. L'objet qui lui est transmis peut être le même, mais il peut avoir des propriétés différentes.

stratégie OnPush

Lorsqu'un composant est marqué avec changeDetection: ChangeDetectionStrategy.OnPush, angulaire supposons que l'entrée de l'objet ne change pas si la référence d'objet n'a pas changé. Ce qui signifie que changer une propriété ne déclenchera pas la détection de changement. Ainsi, la vue ne sera pas synchronisée avec le modèle.

Exemple

ce exemple est cool parce qu'il montre en action. Vous avez un composant parent qui, lorsqu'il est cliqué, modifie les propriétés du nom de l'objet input. Si vous cochez la case click() méthode à l'intérieur du composant parent vous remarquerez qu'il affiche la propriété child component dans la console. Cette propriété a changé..Mais on ne peut pas le voir visuellement. C'est parce que la vue n'a pas été mise à jour. En raison de la stratégie OnPush le processus de détection de changement ne s'est pas produit parce que l'objet ref n'a pas changement.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" (click)="click()">
      <sub-comp [person]="person" #sub></sub-comp>
    </div>
  `
})
export class App {
  person:Person = { name: "thierry" };
  @ViewChild("sub") sub;

  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }
}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      {{person.name}}
    </div>
  `
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

export interface Person{
  name:string,
}

après le clic le nom est toujours thierry dans la vue mais pas dans le composant lui-même


un événement déclenché à l'intérieur d'un composant déclenchera une détection de changement.

voici ce qui m'a troublé dans ma question initiale. La composante ci-dessous est marquée avec la stratégie OnPush, mais la vue est mise à jour quand il changement..

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" >
      <sub-comp ></sub-comp>
    </div>
  `,
  styles:[`
    .orange{ background:orange; width:250px; height:250px;}
  `]
})
export class App {
  person:Person = { name: "thierry" };      
  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }

}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="grey" (click)="click()">
      {{person.name}}
    </div>
  `,
  styles:[`
    .grey{ background:#ccc; width:100px; height:100px;}
  `]
})
export class SubComponent{
  @Input()
  person:Person = { name:"jhon" };
  click(){
    this.person.name = "mich";
  }
}

donc ici nous voyons que l'entrée de l'objet n'a pas changé la référence et nous utilisons la stratégie OnPush. Ce qui pourrait nous faire croire qu'il ne sera pas mis à jour. En fait, il est mis à jour.

comme L'a dit Gunter dans sa réponse, c'est parce que, avec la stratégie OnPush la détection de changement se produit pour un composant si:

  • un événement lié est reçu (clic) sur le composant m'.
  • @Input() a été mis à jour (comme dans le ref obj changé)
  • | async pipe reçu un événement
  • détection de changement a été invoquée "manuellement"

quelle que soit la de la stratégie.

Liens

67
répondu Ced 2018-06-14 07:08:01

*ngFor est ce que c'est propre à la détection de changement. Chaque fois que la détection de changement est exécuté, NgFor obtient son ngDoCheck() méthode appelée et il NgFor vérifie si le contenu du tableau a changé.

dans votre cas il n'y a pas de changement, parce que le constructeur est exécuté avant que L'angle commence à rendre la vue.

Si vous souhaitez par exemple ajouter un bouton

<button (click)="persons.push({name: 'dynamically added', id: persons.length})">add</button>

alors un clic provoquerait en fait un changement qui ngFor a reconnaître.

ChangeDetectionStrategy.OnPush détection de changement dans votre composant serait exécuté, car avec OnPush la détection de changement est exécutée lorsque

  • a lié l'événement est reçu (click)
  • @Input() a été mis à jour par détection de changement
  • | async tuyau a reçu un événement
  • détection de changement a été invoquée "manuellement"
16
répondu Günter Zöchbauer 2017-03-24 06:09:49

> Pour éviter les Application.tick essaie de te détacher de changeDetector:

constructor(private cd: ChangeDetectorRef) {

ngAfterViewInit() {
  this.cd.detach();
}

Plunker

7
répondu yurzui 2016-09-30 16:06:41

en angle, Nous utilisons fortement la structure Parent - enfant. Nous y transmettons des données de parent à enfant en utilisant @Entrées.

là, si un changement se produit sur un ancêtre de l'enfant, la détection du changement se produira vers le bas dans la forme de l'arbre composant cet ancêtre.

mais dans la plupart des situations, nous aurons besoin de mettre à jour la vue de l'enfant (détection de changement d'appel) seulement quand ses entrées changent. Pour parvenir à cela, nous pouvons utiliser OnPush ChangeDetectionStrategy et changer les entrées (en utilisant des immuables) au besoin. LINK

0
répondu Malindu Sandaruwan 2018-07-18 04:18:56