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 ?
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 changeDetector
isChanged()
, 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.
@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..
@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
- https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
- http://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
- https://angular-2-training-book.rangle.io/handout/change-detection/change_detector_classes.html
- https://www.youtube.com/watch?v=X0DLP_rktsc
*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"
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