Utilisation différée de la fonctionnalité de directive dans Angular

Récemment, j'ai dû résoudre le problème du changement de l'ancien mécanisme d'affichage des info-bulles, implémenté au moyen de notre bibliothèque de composants, en un nouveau. Comme toujours, j'ai décidé de ne pas inventer le vélo. Afin de commencer à résoudre ce problème, j'ai commencé à chercher une bibliothèque open source écrite en JavaScript pur qui pourrait être placée dans la directive angulaire et utilisée sous cette forme. Dans mon cas, comme je travaille beaucoup avec popper.js , j'ai trouvé la bibliothèque tippy.js



écrit par le même développeur. Pour moi, une telle bibliothèque ressemblait à la solution parfaite au problème. La bibliothèque tippy.js possède un ensemble complet de fonctionnalités. Avec son aide, vous pouvez créer des info-bulles (éléments d'infobulle) et de nombreux autres éléments. Ces éléments peuvent être personnalisés à l'aide de thèmes, ils sont rapides, fortement typés, offrent une accessibilité au contenu et ont de nombreuses autres fonctionnalités utiles.

J'ai commencé par créer une directive wrapper pour tippy.js:

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  private instance: Instance;
  private _content: string;

  get content() {
    return this._content;
  }

  @Input('tooltip') set content(content: string) {
    this._content = content;
    if (this.instance) this.instance.setContent(content);
  }

  constructor(private host: ElementRef<Element>, private zone: NgZone) {}

  ngAfterViewInit() {
    this.zone.runOutsideAngular(() => {
      this.instance = tippy(this.host.nativeElement, {
        content: this.content,
      });
    });
}

Une info - bulle est créée en appelant une fonction tippyet des éléments passant hostet lui content. De plus, nous appelons en tippydehors de la zone angulaire, car nous n'avons pas besoin des événements consignés tippypour déclencher le cycle de détection des modifications.

Nous allons maintenant utiliser l'info-bulle dans une grande liste de 700 éléments:

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ngFor="let item of data" [tooltip]="item.label">
         {{ item.label }}
      </li>
    </ul>
  `
})
export class AppComponent {
  data = Array.from({ length: 700 }, (_, i) => ({
    id: i,
    label: `Value ${i}`,
  }));
}

Tout fonctionne comme prévu. Chaque élément affiche une info-bulle. Mais nous pouvons mieux résoudre ce problème. Dans notre cas, 700 exemplaires ont été créés tippy. Et pour chaque élément, les outils tippy.js ont ajouté 4 écouteurs d'événements. Cela signifie que nous avons enregistré 2800 auditeurs (700 * 4).

Pour le constater par vous-même, vous pouvez utiliser la méthode getEventListenersdans la console Chrome Developer Tools. La construction de vue getEventListeners(element)renvoie des informations sur les écouteurs d'événements enregistrés pour un élément donné.


Résumé de tous les écouteurs d'événements

Si vous laissez le code sous cette forme, cela peut affecter la consommation de mémoire de l'application et pour le moment de son premier rendu. Cela est particulièrement vrai pour la sortie de page sur les appareils mobiles. Réfléchissons-y. Dois-je créer des instancestippypour les éléments qui ne sont pas affichés dans la fenêtre? Non, il n'en a pas besoin.

Nous utiliserons l'APIIntersectionObserverpour retarder l'inclusion de la prise en charge des info-bulles jusqu'à ce que l'élément apparaisse à l'écran. Si vous n'êtes pas familier avec l'APIIntersectionObserver, jetez un œil à la documentation

Créez pour l'IntersectionObserverencapsuleur représenté par l'objet observé:

const hasSupport = 'IntersectionObserver' in window;

export function inView(
  element: Element,
  options: IntersectionObserverInit = {
    root: null,
    threshold: 0.5,
  }
) {
  return new Observable((subscriber) => {
    if (!hasSupport) {
      subscriber.next(true);
      subscriber.complete();
    }

    const observer = new IntersectionObserver(([entry]) => {
      subscriber.next(entry.isIntersecting);
    }, options);

    observer.observe(element);

    return () => observer.disconnect();
  });
}

Nous avons créé un objet observable qui informe les abonnés du moment où l'élément intersecte avec une zone donnée. De plus, nous vérifions ici le support du IntersectionObservernavigateur. Si le navigateur ne prend pas en charge IntersectionObserver, nous émettons simplement trueet quittons. Les utilisateurs d'IE eux-mêmes sont responsables de leur misère.

Maintenant, inViewnous pouvons utiliser l' objet observé dans une directive qui implémente la fonctionnalité d'info-bulle:

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  ...

  ngAfterViewInit() {
    //   
    inView(this.host.nativeElement).subscribe((inView) => {
      if (inView && !this.instance) {
        this.zone.runOutsideAngular(() => {
          this.instance = tippy(this.host.nativeElement, {
            content: this.content,
          });
        });
      } else if (this.instance) {
        this.instance.destroy();
        this.instance = null;
      }
    });
  }
}

Exécutez à nouveau le code pour analyser le nombre d'écouteurs d'événements.


Résumé de tous les auditeurs d'événements après la finalisation du projet

Excellent. Nous créons maintenant des info-bulles pour les éléments visibles uniquement.

Cherchons des réponses à quelques questions liées à la nouvelle solution.

Pourquoi n'utilisons-nous pas le défilement virtuel pour résoudre ce problème? Le défilement virtuel ne peut être utilisé dans aucune situation. De plus, la bibliothèque de matériaux angulaires met en cache le modèle, par conséquent, les données correspondantes continueront d'occuper la mémoire.

Et la délégation d'événement? Pour ce faire, vous devez implémenter des mécanismes supplémentaires vous-même, dans Angular il n'y a pas de moyen universel pour résoudre ce problème.

Sommaire


Ici, nous avons parlé de la façon de différer l'application de la fonctionnalité des directives. Cela permet à l'application de se charger plus rapidement et de consommer moins de mémoire. L'exemple d'infobulle n'est que l'un des nombreux cas dans lesquels une technique similaire peut être appliquée. Je suis sûr que vous trouverez de nombreuses façons de l'utiliser dans vos propres projets.

Et comment résoudriez-vous le problème de l'affichage d'une grande liste d'éléments, dont chacun doit être équipé d'une info-bulle?

Nous vous rappelons que nous poursuivons le concours de pronostics dans lequel vous pouvez gagner un tout nouvel iPhone. Il est encore temps de s'y attaquer et de faire les prévisions les plus précises sur les valeurs d'actualité.


All Articles