Site icon Blog ARC Optimizer

Simplification de la détection des changements angulaires


La détection de changement est l'épine dorsale du cadre angulaire, et chaque composant a son propre détecteur de changement. Cet article explique les stratégies de détection des modifications et les optimisations pour vous aider à écrire des applications angulaires hautement performantes.

Angular peut détecter lorsque les données changent dans le composant et peut restituer la vue pour afficher les données mises à jour. Angular s'assure que les données du composant et de la vue sont toujours synchronisées les unes avec les autres.

Vous devez avoir utilisé des liaisons angulaires pour afficher les données du composant ou gérer les événements déclenchés sur la vue. Considérons la liste de codes suivante:

 @Component ({
  sélecteur: 'app-root',
  modèle: `
  

{{count}}

» }) la classe d'exportation AppComponent implémente OnInit { compte: nombre = 10; incCount (): void { this.count = this.count +1; } ngOnInit () { } }

Le composant ci-dessus utilise l'interpolation et la liaison d'événement pour afficher des données et appeler une fonction sur l'événement de clic, respectivement. Chaque fois que vous cliquez sur le bouton, la valeur de count augmente de 1 et la vue est mise à jour pour afficher les données mises à jour. Ainsi, vous pouvez voir ici qu'Angular peut détecter les modifications de données dans le composant, puis restituer automatiquement la vue pour refléter la modification.

La partie du cadre angulaire qui fait cela s'appelle le «détecteur de changement». Chaque composant a un détecteur de changement qui lit la liaison sur le modèle et s'assure que le modèle de données et la vue sont synchronisés avec chacun autre. Chaque fois que, pour une raison quelconque (en fait, il y a trois raisons que nous aborderons plus loin dans l'article), le modèle de données change, c'est le détecteur de changement qui projette les données mises à jour sur la vue, de sorte que la vue et le modèle de données soient synchronisés avec L'une et l'autre.

La synchronisation devient complexe lorsque le modèle de données est mis à jour au moment de l'exécution. Jetons un œil à la liste de codes suivante:

 @Component ({
  sélecteur: 'app-root',
  modèle: `
  

{{count}}

» }) la classe d'exportation AppComponent implémente OnInit { compte: nombre = 10; ngOnInit () { setInterval (() => { this.count = this.count + 1; }, 100) } }

Le composant ci-dessus met simplement à jour la valeur de count toutes les 100 millisecondes. Ici, le décompte est un modèle de données qui est mis à jour au moment de l'exécution, mais le détecteur de changement angulaire affiche toujours la valeur mise à jour du décompte toutes les 100 millisecondes en rendant à nouveau la vue.

Ainsi, la partie du cadre angulaire qui garantit que la vue et le modèle de données sont synchronisés l'un avec l'autre est connue sous le nom de détecteur de changement .

Le détecteur de changement vérifie le composant pour la modification des données et restitue la vue pour projeter les données mises à jour.

When Change Detector Runs

Angular suppose que les données du composant ou l'état de l'application entière changent en raison de pour les raisons suivantes, par conséquent il exécute le détecteur de changement lorsque l'une des situations suivantes se produit:

  1. Un événement, tel que cliquer ou soumettre, est déclenché
  2. Un XHR est appelé pour fonctionner avec une API
  3. Un JavaScript asynchrone une fonction, telle que setTimeOut () ou setInterval (), est exécutée

Dans le dernier exemple de code, le composant utilise une méthode JavaScript asynchrone setInterval (), qui met à jour les valeurs du nombre. Puisqu'il s'agit d'une méthode asynchrone, Angular exécute le détecteur de changement pour mettre à jour la vue avec la dernière valeur du compte.

Maintenant, la question se pose: qu'est-ce qui informe Angular de ces opérations asynchrones?

Donc, il y a quelque chose qui s'appelle ngZone dans Angular dont la responsabilité est d'informer Angular de toute opération asynchrone. Nous n'entrerons pas dans les détails de ngZone dans cet article, mais sachez qu'il existe.

Arbre du détecteur de changement

Chaque composant dans Angular possède son propre détecteur de changement.

Le détecteur de changement peut être référencé à l'intérieur du composant en utilisant le service ChageDetectorRef et si nécessaire, vous pouvez injecter le ChageDetectorRef dans un composant en faisant une référence de celui-ci dans le constructeur comme indiqué dans la liste de code suivante:

 classe d'exportation AppComponent implémente OnInit {

  constructeur (cd privé: ChangeDetectorRef) {
    console.log (this.cd);
  }

  ngOnInit () {
    console.log ('init life cycle hook');
  }
} 

Le ChangeDetectorRef fournit diverses API pour travailler avec le détecteur de changement, mais avant de travailler efficacement avec elles, vous devez comprendre l'arborescence des composants.

Chaque composant dans Angular possède son propre détecteur de changement et vous pouvez voir l'ensemble de l'application Angular comme une arborescence de composants. Une arborescence de composants est un graphe orienté et Angular exécute le détecteur de changement de haut en bas dans l'arborescence.

Logiquement, vous pouvez également afficher l'arborescence des composants comme une arborescence de détection de changement car chaque composant a son propre détecteur de changement.

Le détecteur de changement fonctionne de haut en bas dans l'arborescence des composants, et même si un événement est déclenché dans un composant de nœud enfant, Angular exécute toujours le détecteur de changement à partir du composant racine. Par exemple, dans l'arborescence du détecteur de changement ci-dessus, si un événement est déclenché dans le composant CC-121, qui est le composant du nœud inférieur de l'arborescence, Angular exécute toujours le détecteur de changement à partir du nœud du composant racine et pour tous les composants.

Il peut vous venir à l'esprit que, si pour un seul événement quelque part dans l'application, Angular exécute le détecteur de changement pour tous les composants, alors peut-être qu'il peut avoir des problèmes de performances. Cependant, ce n'est pas vrai, pour les raisons suivantes:

  1. L'arbre de composantes angulaires est un graphe orienté, ce qui signifie qu'il y a un flux unidirectionnel du détecteur de changement de la racine vers le bas. Angular sait dans quelle direction l'arbre doit être parcouru, et il n'y a pas de parcours circulaire ou bidirectionnel de l'arbre des détecteurs de changement.
  2. Après un seul passage, l'arbre de détection des changements se stabilise.
  3. Contrairement à AngularJS, dans Angular, il n'y a pas de fonction générique pour mettre à jour la vue. Étant donné qu'ici chaque composant a son propre détecteur de changement, JavaScript VM peut l'optimiser pour de meilleures performances.

Donc, dans Angular, il n'y a pas de fonction générique pour effectuer la liaison, et il génère la classe de détecteur de changement pour chaque composant individuellement au moment de l'exécution. La définition de la classe de détecteur de changement généré est très particulière pour un composant spécifique; par conséquent, JavaScript VM peut l'optimiser pour de meilleures performances.

Réduction du nombre de vérifications

Par défaut, Angular vérifie chaque composant de l'application après tout événement, fonctions JavaScript asynchrones ou appels XHR et, comme vous l'avez vu précédemment, un seul événement déclenché quelque part dans l'arborescence pourrait provoque la vérification de chaque nœud de l'arborescence des composants. Mais il existe un moyen de réduire le nombre de vérifications et vous pouvez éviter d'exécuter le détecteur de changement pour tout le sous-arbre.

Pour optimiser le nombre de vérifications, Angular propose deux stratégies de détection de changement:

  1. Default strategy
  2. onPush strategy

Dans la Default strategy chaque fois que des données des propriétés décorées @Input () sont modifiées , Angular exécute le détecteur de changement pour mettre à jour la vue. Dans la stratégie onPush Angular exécute le détecteur de changement uniquement lorsqu'une nouvelle nouvelle référence est passée aux propriétés décorées @Input ().

Comprenons en jetant un œil à CountComponent:

 import {Component, OnInit, Input} de '@ angular / core';

@Composant({
  sélecteur: 'app-count',
  modèle: `
  

Compte dans l'enfant = {{Counter.count}}

» }) la classe d'exportation CountComponent implémente OnInit { @Input () Counter; constructeur () {} ngOnInit (): void { } }

Le CountComponent a une propriété décorée @Input () Counter, qui accepte les données du composant parent. En outre, le CountComponent est utilisé dans AppComponent, comme indiqué dans la liste de code suivante:

 @Component ({
  sélecteur: 'app-root',
  modèle: `
  

Démonstration du détecteur de changement

` }) la classe d'exportation AppComponent implémente OnInit { Compteur = { nombre: 1 } incCount () { this.Counter.count = this.Counter.count + 1; } ngOnInit () { console.log ('init life cycle hook'); } }

AppComponent utilise CountComponent comme enfant et augmente la valeur du décompte sur le clic du bouton. Ainsi, dès que l'événement de clic est déclenché, Angular exécute le détecteur de changement pour l'ensemble de l'arborescence des composants; par conséquent, vous obtenez une valeur mise à jour du nombre dans le nœud enfant CountComponent.

De plus, chaque fois que les valeurs des propriétés décorées @Input () changent, le détecteur de changement angulaire s'exécute à partir du composant racine et parcourt tous les composants enfants pour mettre à jour la vue.

Ainsi, pour la stratégie de détection de changement par défaut, vous obtenez la sortie comme prévu, mais le défi est, même pour un événement, Angular exécute le détecteur de changement pour l'ensemble de l'arbre. Si vous le souhaitez, vous pouvez l'éviter pour un composant particulier et son sous-arbre en définissant ChangeDetectionStrategy sur onPush .

Le CountComponent est modifié pour utiliser la stratégie onPush comme indiqué dans la liste de code suivante:

 @Component ({
  sélecteur: 'app-count',
  modèle: `
  

Compte dans l'enfant = {{Counter.count}}

`, changeDetection: ChangeDetectionStrategy.OnPush }) la classe d'exportation CountComponent implémente OnInit { @Input () Counter; constructeur () {} ngOnInit (): void { } }

La stratégie de détection de changement onPush indique à Angular d'exécuter le détecteur de changement sur le composant et son sous-arbre uniquement lorsqu'une nouvelle référence est passée aux propriétés décorées @Input.

Pour le moment, AppComponent ne transmet pas une nouvelle référence de l'objet Counter – il change simplement les valeurs de propriété qu'il contient, donc Angular n'exécutera pas le détecteur de changement pour CountComponent; par conséquent, la vue n'afficherait pas la valeur mise à jour du compte.

Vous pouvez comprendre le scénario ci-dessus avec le diagramme ci-dessous:

Le diagramme ci-dessus suppose que pour "Another Child Component", la stratégie de détection des changements est définie sur Par défaut . Par conséquent, en raison du clic sur le bouton dans AppComponent, Angular exécute le détecteur de changement pour chaque nœud du sous-arbre Another Child Component.

Toutefois, pour CountComponent, la stratégie de détection des modifications est définie sur onPush et AppComponent ne transmet pas de nouvelle référence pour la propriété Counter; Par conséquent, Angular n'exécute pas la détection des modifications pour le composant Count et son sous-arbre.

Comme Angular ne vérifie pas CountComponent, la vue n'est pas mise à jour. Pour demander à Angular de vérifier CountComponent et d'exécuter le détecteur de changement, AppComponent doit passer une nouvelle référence de count comme indiqué dans la liste de codes suivante:

 incCount () {

    //this.Counter.count = this.Counter.count + 1;
    this.Counter = {
      count: this.Counter.count + 1
    }
  }

Maintenant les caractéristiques du CountComponent sont les suivantes:

  • Sa stratégie de détection de changement est définie sur onPush
  • Sa propriété décorée @Input () reçoit une nouvelle référence des données

Ainsi, Angular exécute le changez le détecteur pour le CountComponent et son sous-arbre, et vous obtenez des données mises à jour sur la vue. Vous pouvez comprendre le scénario ci-dessus avec le diagramme ci-dessous:

Vous pouvez opter soit pour l'option Default ou stratégie de détection des changements onPush en fonction de vos besoins. Une chose essentielle que vous devez garder à l'esprit est que même si un composant est réglé sur onPush et qu'aucune nouvelle référence ne lui est transmise, Angular exécutera toujours le détecteur de changement pour lui si l'une des situations suivantes se produit:

  1. Un événement, tel que cliquer ou soumettre, est déclenché
  2. Appel XHR pour travailler avec une API
  3. Une fonction JavaScript asynchrone, telle que setTimeOut () ou setInterval (), est exécutée

A Quiz

Conserver ces points dans l'esprit, laissez-moi vous donner un quiz:

  • Pour le CountComponent, la stratégie de détection des changements est définie sur onPush
  • AppComponent ne transmet pas une nouvelle référence au CountComponent

Vous devez maintenant vous assurer que Angular exécute le changer de détecteur pour le CountComponent et mettre à jour la vue. Comment allez-vous y parvenir?

Pour ce faire, vous disposez de l'une des options suivantes:

  1. Exécuter le détecteur de changement manuellement
  2. Effectuez l'une des trois opérations qui provoquent toujours l'exécution de la détection de changement, comme l'exécution d'un événement

Très simplement , vous pouvez mettre un bouton sur le CountComponent pour déclencher un événement, donc exécuter le détecteur de changement.

 @Component ({
  sélecteur: "nombre d'applications",
  modèle: `
  

Count in child = {{Counter.count}}

`, changeDetection: ChangeDetectionStrategy.OnPush }) la classe d'exportation CountComponent implémente OnInit { @Input () Counter; constructeur () {} ngOnInit (): void { } ngDoCheck () { console.log («compter les exécutions du CD du composant»); } }

Maintenant, CountComponent a un bouton Refresh. Un clic sur le bouton Actualiser indiquerait à Angular d'exécuter le détecteur de changement et, en conséquence, la vue sera mise à jour avec la dernière valeur du compteur.

Utilisation des observables

Dans le quiz ci-dessus, l'autre option était d'exécuter le détecteur de changement manuellement. Mais la question principale se pose: comment exécuter manuellement le détecteur de changement?

La réponse est l'utilisation d'observables.

Un observable remarque une mutation dans l'objet sans créer de nouvelle référence pour celui-ci. Ainsi, vous pouvez vous abonner à une observable et, chaque fois qu'un changement se produit, exécuter manuellement le détecteur de changement dans la méthode d'abonnement pour mettre à jour la vue.

Vous pouvez modifier AppComponent pour passer un observable comme suit:

 import {Component, OnInit} from '@ angular / core';
import {BehaviorSubject} de 'rxjs';

@Composant({
  sélecteur: 'app-root',
  modèle: `
  

Démonstration du détecteur de changement

` }) la classe d'exportation AppComponent implémente OnInit { _count = 1; Compteur: tout; incCount () { this.Counter.next ({ count: ++ this._count }) } ngOnInit () { this.Counter = nouveau BehaviorSubject ({ nombre: 0 }) } }

Vous pouvez vous abonner à l'observable dans le CountComponent comme indiqué dans la liste de codes suivante:

 count: any;
  @Input () Counter: Observable ;
  ngOnInit (): void {
    this.Counter.subscribe (données => {
       this.count = data.count;
       console.log (this.count);
    })
  } 

Chaque fois qu'il y a un changement dans l'objet, la méthode subscribe est appelée, vous devez donc exécuter manuellement le détecteur de changement dans la méthode subscribe pour mettre à jour la vue.

Pour exécuter manuellement le détecteur de changement:

  • Injectez le service ChangeDetectorRef dans le composant
  • Utilisez markForCheck dans la méthode d'abonnement pour demander à Angular de vérifier le composant lors de la prochaine exécution des détecteurs de changement.
  • Sur le hook du cycle de vie ngOnDestroy (), désabonnez-vous de l'observable

Vous pouvez modifier le CountComponent pour vous abonner à l'observable et exécuter manuellement le détecteur de changement pour mettre à jour la vue comme indiqué dans la liste de code suivante:

 import {Component, OnInit, Input, ChangeDetectionStrategy, ChangeDetectorRef} depuis '@ angular / core';
import {Observable, Subscription} de 'rxjs';

@Composant({
  sélecteur: 'app-count',
  modèle: `
  

Nombre dans l'enfant = {{count}}

`, changeDetection: ChangeDetectionStrategy.OnPush }) la classe d'exportation CountComponent implémente OnInit, OnInit { compter: tout; countubscription: Abonnement; @Input () Counter: Observable ; constructeur (cd privé: ChangeDetectorRef) { } ngOnInit (): void { this.countsubscription = this.Counter.subscribe ( data => { this.count = data.count; this.cd.markForCheck (); }, err => {console.log (err)}, () => console.log ('complet') ) } ngOnDestroy () { this.countsubscription.unsubscribe (); } }

En utilisant la combinaison de la stratégie onPush et des observables, vous pouvez éviter un plus grand nombre de vérifications dans l'arborescence des composants.

Utilisation du tube asynchrone

Une autre alternative à la méthode subscribe est le tube asynchrone angulaire. En utilisant le tube asynchrone, vous n'avez pas besoin d'appeler manuellement le détecteur de changement, de vous abonner à l'observable et de vous désabonner de l'observable car le tube asynchrone effectue toutes ces tâches pour vous.

  • Pour la stratégie de détection de changement onPush, si un changement de données observable se produit, le tube async marque automatiquement le composant pour le contrôle
  • Lors de la destruction du composant, le tube async désabonne automatiquement l'observable, évitant ainsi les risques de fuite de mémoire potentielle [19659064] Vous pouvez utiliser un tube asynchrone dans le CountComponent comme indiqué dans la liste de code suivante:
     @Component ({
      sélecteur: 'app-count',
      modèle: `
      

    {{data.count}}

    `, changeDetection: ChangeDetectionStrategy.OnPush }) la classe d'exportation CountComponent implémente OnInit { @Input () Counter: Observable ; ngOnInit (): void { } }

    Le tube asynchrone est une approche plus propre, et il est recommandé de l'utiliser tout en travaillant avec des données observables et une stratégie de détection de changement onPush.

    Détacher le détecteur de changement

    Il existe un autre moyen plus agressif de réduire les contrôles pour un composant et son sous-arbre, en détachant le détecteur de changement du composant:

     constructor (private cd: ChangeDetectorRef) {
        this.cd.detach ();
      }
    

    Vous pouvez éviter de vérifier le composant et son sous-arbre en détachant le détecteur de changement. Pour un détecteur de changement détaché:

    1. Angular ne vérifie pas le composant ou son sous-arbre.
    2. Angular ne mettra pas à jour la vue et n'effectuera pas les liaisons.

    Vous pouvez comprendre le scénario ci-dessus avec le diagramme ci-dessous:

    Vous pouvez modifier le CountComponent pour détacher puis rattacher le détecteur de changement comme indiqué dans la liste de codes suivante :

     @Component ({
      sélecteur: 'app-count',
      modèle: `
      

    {{title}}

    Count in child = {{Counter.count}}

    `, changeDetection: ChangeDetectionStrategy.Default }) la classe d'exportation CountComponent implémente OnInit { @Input () Counter; title = "Détacher le composant"; constructeur (cd privé: ChangeDetectorRef) { this.cd.detach (); } attachcd () { this.cd.reattach (); } ngOnInit (): void { } ngDoCheck () { console.log ('compter les exécutions du CD du composant'); } }

    Angular n'exécutera pas le détecteur de changement pour CountComponent car son détecteur de changement est détaché. En outre, Angular n'effectuera pas la liaison sur le modèle et, en tant que sortie, vous n'obtiendrez pas le titre et le décompte rendus sur le modèle. Lorsque vous cliquez sur le bouton Actualiser, le détecteur de changement est rattaché et vous constaterez que la vue est mise à jour et rend toutes les liaisons.

    Vous pouvez judicieusement détacher un détecteur de changement d'un composant pour réduire le nombre de contrôles.

    detectChanges et markForCheck

    Le ChangeDetectorRef a deux méthodes supplémentaires:

    1. detectChanges
    2. markForCheck

    La méthode detectChanges exécute le détecteur de changement pour le composant actuel et ses enfants. Pour une fois, il peut même exécuter la détection de changement sur un composant qui a détaché le détecteur de changement sans le rattacher.

    Compte tenu de l'exemple ci-dessus, au lieu de rattacher le détecteur de changement, vous pouvez vérifier le composant une fois et mettre à jour la vue en utilisant le detectChanges .

     attachcd () {
        //this.cd.reattach ();
        this.cd.detectChanges ();
      } 

    Ici, Angular ne rattache pas le détecteur de changement et ne vérifie le composant qu'une seule fois. Donc, essentiellement, le composant ne sera pas vérifié pendant les cycles de détection de changement réguliers suivants.

    D'autre part, la méthode markForCheck vous permet de vérifier tous les composants parents jusqu'au composant racine. Ainsi, en utilisant la méthode markForCheck, vous pouvez marquer tous les composants jusqu'au composant racine à vérifier lors du prochain cycle de détection de changement.

    Dans un scénario réel, vous pouvez utiliser markForCheck en combinaison avec la méthode reattach, car la méthode reattach ne fonctionne pas pour un composant si le détecteur de changement de son composant parent est désactivé. Dans ce cas, vous devez utiliser la méthode markForCheck pour vous assurer qu'Angular vous permet de vérifier tous les composants parents jusqu'au composant racine.

    Vous pouvez illustrer les discussions ci-dessus sur les différentes méthodes dans un diagramme comme suit:

    Résumé

    Vous comprenez maintenant le mécanisme de détection de changement angulaire et les différentes options disponibles avec lui. Vous devez choisir une stratégie de détection des modifications par défaut ou onPush en fonction des besoins. Pour réduire le nombre de vérifications, vous pouvez envisager de détacher le détecteur de changement d'un composant et d'utiliser reattach ou detectChanges selon vos besoins.

    J'espère que vous trouverez cet article utile et qu'il vous aidera à écrire des applications angulaires plus performantes.





Source link
Quitter la version mobile