3 façons de rendre de grandes listes en angulaire

En 2020, les cadres frontaux sont devenus meilleurs, plus efficaces et plus rapides. Mais, même dans cet esprit, le rendu de grandes listes sans geler le navigateur peut toujours être une tâche intimidante même pour les cadres existants les plus rapides.

C'est l'un de ces cas où «le cadre est rapide et le code est lent». Il existe de nombreuses approches qui vous permettent d'afficher un grand nombre d'éléments sans bloquer l'interaction de l'utilisateur avec la page Web. L'auteur de l'article, dont nous publions aujourd'hui la traduction, souhaite explorer les méthodes existantes d'affichage de grandes listes sur des pages Web et parler de leurs domaines d'application.







Bien que ce matériel soit destiné à Angular, ce qui est discuté ici s'applique à d'autres frameworks et projets qui sont écrits en JavaScript pur. En particulier, les approches suivantes pour rendre de grandes listes seront considérées ici:

  • Défilement virtuel (en utilisant Angular CDK).
  • Rendu manuel.
  • Rendu progressif.

Défilement virtuel


Le défilement virtuel est peut-être le moyen le plus efficace de travailler avec de grandes listes. Certes, il a quelques inconvénients. Le défilement virtuel, grâce au Angular CDK et à d'autres plugins, est très facile à implémenter dans n'importe quel composant.

L'idée derrière le défilement virtuel est simple, mais pas toujours facile à mettre en œuvre. L'essentiel ici est que nous avons un conteneur et une liste d'éléments. Un élément n'est rendu que s'il se trouve dans la zone visible du conteneur.

Nous utiliserons le module scrollingdu CDK angulaire, qui est conçu pour organiser le défilement virtuel. Pour ce faire, vous devez d'abord installer le CDK:

npm i @angular/cdk

Ensuite, vous devez importer le module:

import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
 ...
 imports: [ ScrollingModule, ...]
})
export class AppModule {}

Après cela, dans les composants, vous pouvez utiliser cdk-virtual-scroll-viewport:

<cdk-virtual-scroll-viewport itemSize="50">
 <div *cdkVirtualFor="let item of items">
   {{ item }}
 </div>
</cdk-virtual-scroll-viewport>

Voici un exemple de projet qui utilise cette approche pour organiser le défilement virtuel.

Comme vous pouvez le voir, les mécanismes angulaires standard permettent, sans trop de difficulté, d'obtenir des résultats impressionnants. Le composant dans l'exemple rend plusieurs milliers d'éléments sans aucun problème.

Si le défilement virtuel est si bon et s'il est si facile à mettre en œuvre, la question se pose de savoir pourquoi étudier d'autres façons de rendre de grandes listes. J'étais également intéressé par cette question. Il s'est avéré que cette situation a plusieurs raisons:

  • , , . , . , Autocomplete ( ). , , , , . — .
  • — , .
  • Il y a quelques problèmes d'accessibilité et de facilité d'utilisation du contenu d'une liste avec défilement virtuel. Les éléments masqués ne sont pas rendus - cela signifie qu'ils ne seront pas disponibles pour les lecteurs d'écran et qu'ils ne peuvent pas être trouvés sur la page à l'aide des mécanismes de navigateur standard.

Le défilement virtuel est idéal dans un certain nombre de situations (à condition que cela fonctionne):

  • Dans ce cas, si vous souhaitez afficher des listes dont la taille n'est pas connue à l'avance, ou celles qui peuvent être énormes (selon une estimation approximative, des listes qui comprennent plus de 5000 éléments, mais cela dépend grandement de la complexité de chaque élément).
  • Dans le cas où vous devez organiser un défilement sans fin.

Rendu manuel


L'une des façons de travailler avec des listes que j'ai essayé d'accélérer la sortie d'un grand ensemble d'éléments est d'utiliser à la place le rendu manuel à l'aide de l'API angulaire *ngFor.

Nous avons un modèle simple qui utilise une boucle organisée en utilisant la directive *ngFor

<tr 
*ngFor="let item of data; trackBy: trackById; let isEven = even; let isOdd = odd"
    class="h-12"
    [class.bg-gray-400]="isEven"
    [class.bg-gray-500]="isOdd"
>
  <td>
    <span class="py-2 px-4">{{ item.id }}</span>
  </td>

  <td>
    <span>{{ item.label }}</span>
  </td>

  <td>
    <a>
      <button class="py-2 px-4 rounded (click)="remove(item)">x</button>
    </a>
  </td>
</tr>

Pour mesurer les performances de rendu de 10 000 éléments simples, j'ai utilisé un benchmark basé sur js-frameworks-benchmark .

Tout d'abord, j'ai examiné les performances d'une liste, qui utilise une boucle régulière pour sortir *ngFor. En conséquence, il s'est avéré que l'exécution du code (Scripting) a pris 1099 ms., Le rendu (Rendering) a pris 1553 ms., Et le dessin (Painting) - 3 ms.


Une étude des performances de la liste, qui utilise * ngFor pour afficher la sortie. Les

éléments de la liste peuvent être rendus manuellement à l'aide de l'API angulaire:

<tbody>
  <ng-container #itemsContainer></ng-container>
</tbody>
<ng-template #item let-item="item" let-isEven="isEven">
  <tr class="h-12 "
      [class.bg-gray-400]="isEven"
      [class.bg-gray-500]="!isEven"
  >
    <td>
      <span class="py-2 px-4">{{ item.id }}</span>
    </td>

    <td>
      <span>{{ item.label }}</span>
    </td>

    <td>
      <a>
        <button class="py-2 px-4 rounded" (click)="remove(item)">x</button>
      </a>
    </td>
  </tr>
</ng-template>

Parlons de la façon dont le code du contrôleur a changé.

Nous avons déclaré un modèle et un conteneur:

@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChild('item', { read: TemplateRef }) template: TemplateRef<any>;

Lors de la génération de données, nous les rendons à l'aide de la méthode d' createEmbeddedViewentité ViewContainerRef:

private buildData(length: number) {
  const start = this.data.length;
  const end = start + length;

  for (let n = start; n <= end; n++) {
    this.container.createEmbeddedView(this.template, {
      item: {
        id: n,
        label: Math.random()
      },
      isEven: n % 2 === 0
    });
  }
}

En conséquence, les indicateurs caractérisant les performances de la liste ont été légèrement améliorés. A savoir, il a fallu 734 ms pour exécuter le code, 1443 ms pour restituer, 2 ms pour dessiner.


La recherche des performances d'une liste qui utilise l'API angulaire pour afficher

True, dans la pratique, cela signifie que la liste est toujours extrêmement lente. Lorsque vous cliquez sur le bouton approprié, le navigateur «se fige» pendant quelques secondes. Si cela apparaissait en production, les utilisateurs ne l'apprécieraient certainement pas.

Voici à quoi cela ressemble (ici, à l'aide de la souris, j'imite l'indicateur de chargement).


Ralentissement de la liste

Maintenant, pour améliorer le rendu manuel des listes, nous allons essayer d'appliquer la technologie de rendu progressif.

Rendu progressif


L'idée du rendu progressif est simple. Il consiste à restituer progressivement un sous-ensemble d'éléments, à reporter le rendu des autres éléments dans la boucle d'événement. Cela permet au navigateur d'afficher tous les éléments sans "ralentir".

Le code ci-dessous qui implémente le rendu de liste progressif est très simple:

  • D'abord, en utilisant ceci setInterval, nous établissons un appel régulier, toutes les 10 ms. Une fonction qui rend 500 éléments lorsqu'elle est appelée.
  • Une fois tous les éléments déduits, que nous déterminons sur la base de l'analyse de l'index de l'élément courant, nous arrêtons l'appel de fonction régulier et interrompons le cycle.

private buildData(length: number) {
  const ITEMS_RENDERED_AT_ONCE = 500;
  const INTERVAL_IN_MS = 10;

  let currentIndex = 0;

  const interval = setInterval(() => {
    const nextIndex = currentIndex + ITEMS_RENDERED_AT_ONCE;

    for (let n = currentIndex; n <= nextIndex ; n++) {
      if (n >= length) {
        clearInterval(interval);
        break;
      }
      const context = {
        item: {
          id: n,
          label: Math.random()
        },
        isEven: n % 2 === 0
      };
      this.container.createEmbeddedView(this.template, context);
    }

    currentIndex += ITEMS_RENDERED_AT_ONCE;
  }, INTERVAL_IN_MS);

Veuillez noter que le nombre d'éléments affichés par appel à la fonction en cours de transfert setInterval, ainsi que la fréquence de l'appel à cette fonction, dépendent entièrement des circonstances. Par exemple, si les éléments de la liste affichée sont très complexes, la sortie de 500 de ces éléments en une seule fois sera trop lente.

En mesurant les performances de cette solution, j'ai obtenu des résultats plus mauvais que ceux que j'ai reçus plus tôt. Exécution de code - 907 ms., Rendu - 2555 ms., Dessin - 16 ms.


Rechercher les performances d'une liste, dont la sortie utilise un rendu progressif,

mais l'utilisateur, travaillant avec une telle liste, ressentira des sensations beaucoup plus agréables qu'auparavant. Bien que le temps requis pour rendre la liste ait augmenté, l'utilisateur ne le remarquera pas. Nous rendons en une seule fois 500 éléments. Dans ce cas, le rendu est effectué en dehors des limites du conteneur.

Ici, certains problèmes peuvent survenir du fait que le conteneur change de taille pendant le rendu ou que la position du défilement de contenu change. Si cela se produit, vous devrez faire face à des problèmes similaires.

Voici à quoi ressemble une telle liste.


La liste est rapide

Sommaire


Les techniques de rendu manuel et progressif pour les grandes listes sont certainement utiles dans certaines situations. Je les ai utilisés dans des cas où le défilement virtuel pour une raison quelconque ne me convenait pas.

Compte tenu de ce qui précède, nous pouvons dire que le plus souvent le défilement virtuel, construit sur la base d'une bonne bibliothèque, comme le CDK angulaire, est le meilleur moyen d'afficher de grandes listes. Mais si pour une raison quelconque le défilement virtuel ne peut pas être utilisé, le développeur a d'autres possibilités.

Chers lecteurs! Comment sortez-vous de grandes listes dans vos projets Web?


All Articles