Fermer

mai 2, 2018

Tout ce que vous devez savoir –


Cet article sur la détection des changements dans Angular a été initialement publié sur le blog Angular In Depth et est republié ici avec la permission.

Si vous êtes comme moi et cherchez à acquérir une compréhension globale de le mécanisme de détection de changement dans Angular, vous devez essentiellement explorer les sources, car il n'y a pas beaucoup d'informations disponibles sur le web.

La plupart des articles mentionnent que chaque composant possède son propre détecteur de changement qui est chargé de vérifier le composant. Ne pas aller au-delà de cela et surtout se concentrer sur des cas d'utilisation pour immutables et stratégie de détection de changement.

Cet article vous fournit l'information nécessaire pour comprendre pourquoi cas d'utilisation avec des travaux immutables et ] la stratégie de détection du changement affecte la vérification. En outre, ce que vous apprendrez de cet article vous permettra de proposer différents scénarios pour l'optimisation des performances par vous-même.

La première partie de cet article est assez technique et contient beaucoup de liens vers les sources. Il explique en détail comment le mécanisme de détection de changement fonctionne sous le capot. Son contenu est basé sur la version angulaire la plus récente (4.0.1 au moment de l'écriture). La façon dont le mécanisme de détection de changement est mis en œuvre sous le capot dans cette version est différente de la précédente 2.4.1. Si vous êtes intéressé, vous pouvez lire un peu comment cela a fonctionné dans cette réponse Stack Overflow .

La seconde moitié de l'article montre comment la détection de changement peut être utilisée dans l'application, et son contenu est applicable 2.4.1 et les versions 4.0.1 les plus récentes d'Angular, puisque l'API publique n'a pas changé

Voir comme un concept de base

Une application angulaire est un arbre de composants. Cependant, sous le capot, Angular utilise une abstraction de bas niveau appelée view . Il existe une relation directe entre une vue et un composant: une vue est associée à un composant et vice versa. Une vue contient une référence à l'instance de la classe de composant associée dans la propriété . Toutes les opérations – comme les vérifications de propriétés et les mises à jour DOM – sont effectuées sur les vues. Par conséquent, il est plus correct d'affirmer qu'Angular est un arbre de vues, alors qu'un composant peut être décrit comme un concept de niveau supérieur d'une vue. Voici ce que vous pouvez lire sur la vue dans les sources :

Une vue est un bloc de construction fondamental de l'interface utilisateur de l'application. C'est le plus petit groupe d'éléments qui sont créés et détruits ensemble.

Les propriétés des éléments dans une vue peuvent changer, mais la structure (nombre et ordre) des éléments dans une vue ne le peut pas. La modification de la structure des éléments ne peut être effectuée qu'en insérant, déplaçant ou supprimant des vues imbriquées via ViewContainerRef. Chaque vue peut contenir de nombreux conteneurs View

Dans cet article, je vais utiliser les notions de composants et de composants de manière interchangeable.

Il est important de noter ici que tous les articles sur le web et réponses sur Stack Overflow En ce qui concerne la détection des modifications, reportez-vous à la vue que je décris ici sous le nom Change Detector Object ou ChangeDetectorRef. En réalité, il n'y a pas d'objet séparé pour la détection de changement et View est ce que la détection de changement fonctionne.

Chaque vue a un lien vers ses vues enfants via la propriété nodes

View State

Chaque vue a un état ce qui joue un rôle très important car, en fonction de sa valeur, Angular décide s'il faut exécuter la détection de changement pour la vue et ] tous ses enfants ou passez le. Il existe plusieurs états possibles mais les suivants sont pertinents dans le contexte de cet article:

  1. FirstCheck
  2. ChecksEnabled
  3. Errored
  4. Détruit

La détection de changement est ignorée pour le vue et ses vues enfants si ChecksEnabled est false ou la vue est dans l'état Errored ou Destroyed . Par défaut, toutes les vues sont initialisées avec ChecksEnabled sauf si ChangeDetectionStrategy.OnPush est utilisé. Plus sur cela plus tard. Les états peuvent être combinés: par exemple, une vue peut avoir à la fois les drapeaux FirstCheck et ChecksEnabled .

Angular a un tas de concepts de haut niveau pour manipuler les vues. J'ai écrit sur certains d'entre eux ici . Un tel concept est ViewRef . Il encapsule la vue de composant sous-jacente et possède une méthode bien nommée detectChanges . Lorsqu'un événement asynchrone a lieu, Angular déclenche la détection de changement sur son ViewRef le plus haut, qui après avoir exécuté la détection de changement exécute la détection de changement pour ses vues enfant . viewRef est ce que vous pouvez injecter dans un constructeur de composant en utilisant le [: ChangeDetectorRef jeton:

 classe d'exportation AppComponent {
    constructeur (cd: ChangeDetectorRef) {...}

Cela peut être vu à partir de la définition de la classe:

 export declare abstract class ChangeDetectorRef {
    abstract checkNoChanges (): void;
    abstract detach (): void;
    abstract detectChanges (): void;
    résumé markForCheck (): void;
    résumé reattach (): void;
}

classe abstraite d'exportation ViewRef extends ChangeDetectorRef {
   ...
}

Opérations de détection des modifications

La principale logique responsable de l'exécution de la détection des modifications pour une vue réside dans la fonction checkAndUpdateView . La plupart de ses fonctionnalités effectuent des opérations sur les vues de composant enfant . Cette fonction est appelée récursivement pour chaque composant, à partir du composant hôte. Cela signifie qu'un composant enfant devient un composant parent lors de l'appel suivant lorsqu'un arbre récursif se déploie.

Lorsque cette fonction est déclenchée pour une vue particulière, elle effectue les opérations suivantes dans l'ordre spécifié:

  1. sets ViewState.firstCheck à true si une vue est vérifiée pour la première fois et false si elle a déjà été vérifiée avant
  2. vérifie et met à jour les propriétés d'entrée sur un composant / instance de directive enfant
  3. mises à jour état de détection de modification de vue enfant (partie de l'implémentation de stratégie de détection de changement)
  4. exécute la détection de changement les étapes dans la liste)
  5. appels OnChanges crochet de cycle de vie sur un composant enfant si les liaisons ont changé
  6. appels OnInit et ngDoCheck sur un composant enfant ( OnInit est calle d uniquement lors de la première vérification)
  7. mises à jour ContentChildren liste de requêtes sur une instance de composant vue enfant
  8. appels AfterContentInit et AfterContentChecked lifecycle hooks sur l'occurrence du composant enfant ( AfterContentInit est appelé uniquement lors du premier contrôle)
  9. met à jour les interpolations DOM pour la vue actuelle si les propriétés sur vue instance de composant modifiée
  10. exécute la détection de changement pour une vue enfant (répète les étapes de cette liste)
  11. mises à jour ViewChildren liste de requêtes sur le courant Voir l'instance du composant
  12. appelle AfterViewInit et AfterViewChecked crochets de cycle de vie sur l'occurrence du composant enfant ( AfterViewInit est appelé uniquement lors de la première vérification)
  13. désactive les contrôles f ou la vue actuelle (partie de l'implémentation de la stratégie de détection de changement)

Il y a peu de choses à mettre en évidence basées sur les opérations listées ci-dessus

La première chose est que le hook de cycle de vie onChanges est déclenché. Le composant enfant avant la vue enfant est vérifié, et il sera déclenché même si la détection modifiée pour la vue enfant sera ignorée. Ceci est une information importante, et nous verrons comment nous pouvons exploiter cette connaissance dans la deuxième partie de l'article.

La deuxième chose est que le DOM pour une vue est mis à jour dans le cadre d'un mécanisme de détection de changement alors que la vue est en cours de vérification Cela signifie que si un composant n'est pas vérifié, le DOM n'est pas mis à jour même si les propriétés de composant utilisées dans un modèle changent. Les modèles sont rendus avant la première vérification. Ce que je qualifie de mise à jour DOM est en fait une mise à jour par interpolation. Donc, si vous avez du {{name}} l'élément DOM span sera rendu avant la première vérification. Pendant la vérification, seule la partie {{name}} sera rendue

Une autre observation intéressante est que l'état d'une vue de composant enfant peut être modifié pendant la détection de changement. J'ai mentionné précédemment que toutes les vues de composant sont initialisées avec ChecksEnabled par défaut, mais pour tous les composants utilisant la stratégie OnPush la détection de changement est désactivée après le premier contrôle (opération 9 dans la liste ):

 if (view.def.flags & ViewFlags._OnPush_) {
  view.state & = ~ ViewState._ChecksEnabled_;
}

Cela signifie que lors de la détection de changement suivante, la vérification sera ignorée pour cette vue de composant et tous ses enfants. La documentation sur la stratégie OnPush indique qu'un composant sera vérifié uniquement si ses liaisons ont changé. Pour ce faire, les contrôles doivent être activés en définissant le bit ChecksEnabled . Et c'est ce que fait le code suivant (opération 2):

 if (compView.def.flags & ViewFlags._OnPush_) {
  compView.state | = ViewState._ChecksEnabled_;
}

L'état est mis à jour uniquement si les liaisons de vues parent ont été modifiées et la vue de composant enfant a été initialisée avec ChangeDetectionStrategy.OnPush .

Enfin, la détection de changement pour la vue actuelle est responsable vues (opération 8). C'est l'endroit où l'état de la vue du composant enfant est vérifié et si c'est ChecksEnabled alors pour cette vue la détection de changement est effectuée. Voici le code pertinent:

 viewState = view.state;
...
case ViewAction._CheckAndUpdate_:
  if ((viewState & ViewState._ChecksEnabled_) &&
    (viewState & (ViewState._Errored_ | ViewState._Destroyed_)) === 0) {
    checkAndUpdateView (voir);
  }
}

Vous savez maintenant que l'état d'affichage contrôle si la détection des modifications est effectuée pour cette vue et ses enfants ou non. La question est donc la suivante: pouvons-nous contrôler cet état? Il se trouve que nous pouvons, et c'est ce que traite la deuxième partie de cet article.

Certains hooks de cycle de vie sont appelés avant la mise à jour du DOM (3,4,5) et certains après (9). Donc, si vous avez la hiérarchie des composants A -> B -> C voici l'ordre des mises à jour des appels et des liaisons de hooks:

 A: AfterContentInit
A: AfterContentChecked
A: Mettre à jour les liaisons
    B: AfterContentInit
    B: AfterContentChecked
    B: mettre à jour les liaisons
        C: AfterContentInit
        C: AfterContentChecked
        C: mettre à jour les liaisons
        C: AfterViewInit
        C: AfterViewChecked
    B: AfterViewInit
    B: AfterViewChecked
A: AfterViewInit
A: AfterViewChecked

Exploration des implications

Supposons que nous ayons l'arbre de composants suivant:

 Un arbre de composants

Comme nous l'avons appris plus haut, chaque composant est associé à une vue de composant. Chaque vue est initialisée avec le ViewState.ChecksEnabled ce qui signifie que lorsque Angular exécute la détection de changement, chaque composant de l'arbre sera vérifié.

Supposons que nous voulions désactiver la détection de changement pour le composant et ses enfants. C'est facile à faire – il suffit de régler ViewState.ChecksEnabled à false . Changer d'état est une opération de bas niveau, donc Angular nous fournit un tas de méthodes publiques disponibles sur la vue. Chaque composant peut obtenir sa vue associée via le jeton ChangeDetectorRef . Pour cette classe, les documents Angular définissent l'interface publique suivante:

 class ChangeDetectorRef {
  markForCheck (): void
  detach (): void
  reattach (): void

  detectChanges (): void
  checkNoChanges (): void
}

Voyons comment nous pouvons l'arranger à notre avantage

detach

La première méthode qui nous permet de manipuler l'état est detach qui désactive simplement les contrôles pour la vue actuelle: [19659025] detach (): void {this._view.state & = ~ ViewState._ChecksEnabled_; }

Voyons comment il peut être utilisé dans le code:

 export class AComponent {
  constructeur (public cd: ChangeDetectorRef) {
    this.cd.detach ();
  }

Cela garantit que, pendant la détection de changement suivante, la branche gauche commençant par AComponent sera ignorée (les composants orange ne seront pas vérifiés):

 Les composants orange sont sautés ]

Il y a deux choses à noter ici. La première est que, même si nous avons changé l'état de AComponent tous ses composants enfants ne seront pas vérifiés également. Deuxièmement, étant donné qu'aucune détection de changement ne sera effectuée pour les composants de branche de gauche, le DOM de leurs modèles ne sera pas non plus mis à jour. Voici un petit exemple pour le démontrer:

 @Component ({
  sélecteur: 'a-comp',
  template: ` Voyez si je change: {{changed}} `})
classe d'exportation AComponent {
  constructeur (public cd: ChangeDetectorRef) {
    this.changed = 'faux';

    setTimeout (() => {
      this.cd.detach ();
      this.changed = 'vrai';
    }, 2000);
  }

La première fois que le composant est vérifié, le span sera rendu avec le texte Voir si je change: false . Et dans les deux secondes, lorsque la propriété a changé est mise à jour à true le texte de la plage ne sera pas modifié. Cependant, si nous supprimons la ligne this.cd.detach () tout fonctionnera comme prévu.

reattach

Comme indiqué dans la première partie de l'article, le OnChanges hook de cycle de vie sera toujours déclenché pour AComponent si la liaison d'entrée aProp est modifiée sur AppComponent . Cela signifie que, une fois que nous sommes informés que les propriétés d'entrée changent, nous pouvons activer le détecteur de changement pour que le composant actuel exécute la détection de changement et le détacher au prochain tick. Voici l'extrait qui démontre que:

 export class AComponent {
  @Input () inputAProp;

  constructeur (public cd: ChangeDetectorRef) {
    this.cd.detach ();
  }

  ngOnChanges (valeurs) {
    this.cd.reattach ();
    setTimeout (() => {
      this.cd.detach ();
    })
  }

Cela est dû au fait que rattache simplement définit bit ViewState.ChecksEnabled :

 reattach (): void {this._view.state | = ViewState.ChecksEnabled; }

Ceci est presque équivalent à ce qui est fait quand ChangeDetectionStrategy est réglé sur OnPush : il désactive la vérification après la première exécution de détection de changement, et l'active quand la propriété parent et désactive après l'exécution.

Notez que le hook OnChanges est uniquement déclenché pour le composant le plus haut de la branche désactivée, et non pour chaque composant de la branche désactivée.

markForCheck [19659071] La méthode reattach active uniquement les vérifications pour le composant en cours, mais si la détection de modification n'est pas activée pour son composant parent, elle n'aura aucun effet. Cela signifie que la méthode reattach n'est utile que pour le composant le plus haut de la branche désactivée

Nous avons besoin d'un moyen de permettre la vérification de tous les composants parents jusqu'au composant racine. Et il y a une méthode pour cela – markForCheck :

 let currView: ViewData | null = view;
while (currView) {
  if (currView.def.flags & ViewFlags._OnPush_) {
    currView.state | = ViewState._ChecksEnabled_;
  }
  currView = currView.viewContainerParent || currView.parent;
}

Comme vous pouvez le voir à partir de l'implémentation, il ne fait qu'illérer vers le haut et permet des vérifications pour chaque composant parent jusqu'à la racine.

Quand est-ce utile? Tout comme avec ngOnChanges le hook de cycle de vie ngDoCheck est déclenché même si le composant utilise la stratégie OnPush . Encore une fois, il est uniquement déclenché pour le composant le plus haut de la branche désactivée, et non pour chaque composant de la branche désactivée. Mais nous pouvons utiliser ce hook pour effectuer une logique personnalisée et marquer notre composant éligible pour un cycle de détection de changement. Puisque Angular ne vérifie que les références d'objets, nous pouvons implémenter la vérification de certaines propriétés d'objets:

 Component ({
  ...,
  changeDetection: ChangeDetectionStrategy.OnPush
})
MyComponent {
  Éléments @Input ();
  prevLength;
  constructeur (cd: ChangeDetectorRef) {}

  ngOnInit () {
    this.prevLength = this.items.length;
  }

  ngDoCheck () {
    if (this.items.length! == this.prevLength) {
      this.cd.markForCheck ();
      this.prevLenght = this.items.length;
    }
  }

detectChanges

Il existe un moyen d'exécuter la détection de changement une fois pour le composant actuel et tous ses enfants. Ceci est fait en utilisant la méthode detectChanges . Cette méthode exécute la détection des modifications pour la vue du composant en cours, quel que soit son état, ce qui signifie que les vérifications peuvent rester désactivées pour la vue en cours et que le composant ne sera pas vérifié. Voici un exemple:

 export class AComponent {
  @Input () inputAProp;

  constructeur (public cd: ChangeDetectorRef) {
    this.cd.detach ();
  }

  ngOnChanges (valeurs) {
    this.cd.detectChanges ();
  }

Le DOM est mis à jour lorsque la propriété d'entrée change, même si la référence du détecteur de changement reste détachée

checkNoChanges

Cette dernière méthode disponible sur le détecteur de changement garantit qu'aucune modification ne sera effectuée changer la détection. Fondamentalement, il effectue les opérations 1,7 et 8 de la liste ci-dessus et déclenche une exception s'il trouve une modification de liaison ou détermine que DOM doit être mis à jour




Source link