Accélérez le rendu de MatTable en quelques étapes

Il arrive souvent que la majeure partie de l'application soit composée de différentes listes et tableaux. Afin de ne pas réinventer la roue à chaque fois, j'ai, comme beaucoup, souvent utilisé des tables de matériaux angulaires .

Plus tard, ces tableaux ont augmenté, les données textuelles et les éléments imbriqués ont été placés dans des cellules. Tout cela grandit et devient une bonne charge sur la machine de l'utilisateur. Et bien sûr, personne n'aime ça.

Dans mon dernier projet à domicile, il y avait une table dont les cellules étaient pour la plupart remplies de différents champs (on pourrait même dire que c'était une seule grande forme).

Et le rendu a pris environ 8 secondes (table 40 x 40).

Alors, comment pouvez-vous optimiser MatTable pour les grandes listes?

image

Cas de test


Afin d'aider les autres à résoudre ce problème, j'ai écrit une petite application de test. Tous la même table MatTable, avec cinq colonnes (la première est l'id de l'élément, les autres sont des champs de texte MatFormField normaux).



Il semblerait que ce soit une simple table, mais même en rendant 100 le drain d'une telle table ne prend ni plus ni moins de 2 secondes (Oh, ce matériau et ses performances "top").



Le rendu lui-même dans ce cas a pris seulement (860 ms), cependant, la page a gelé pendant 2,2 secondes et l'utilisateur était bouleversé.

Eh bien, essayons de rendre un tableau avec 300 lignes. Et le roulement de tambour, on attend un peu et on voit que près de 10 secondes ont été consacrées au script et au rendu au total. Autrement dit, dans le cas où l'utilisateur souhaite afficher un tel tableau sur 300 lignes, sa page se fige pendant 10 secondes. C'est très effrayant et étonnant (en fait pas).



En fait, j'essayais toujours de mesurer le temps qu'il faudrait pour dessiner 1000 éléments, mais mon pathétique i7 ne pouvait pas le supporter et la page tombait constamment.
Nous essaierons de le faire plus tard avec la solution déjà appliquée.

Solution au problème


Et donc, tout le monde peut prendre des mesures à l'aide d'utilitaires basés sur un navigateur, mais il n'y a pas de solution à ce problème.

Cette décision m’a conduit à raisonner sur l’essence même du problème.
une bonne compréhension du problème représente déjà au moins la moitié de sa solution.
Je pense que tout le monde comprend que cela se produit parce que les données de la table sont d'abord traitées par des scripts, puis crachées en un seul morceau . À son tour, c'est précisément pourquoi nous voyons une suspension caractéristique.

Problème trouvé. Reste maintenant à découvrir comment le résoudre. La toute première solution qui vient à l'esprit est la plus simple. Si le problème traite toutes les données en même temps, vous devez faire en sorte que la table traite les données en plusieurs parties.

Mais comment faire ça?

Pensons à ce que nous avons pour cela hors de la boîte. La première chose qui me vient à l'esprit est la fonction Track By. Lors de la modification de la source de données, la table entière ne sera pas restituée, mais uniquement ses modifications.

Ajoutons cette fonction à notre tableau:

<mat-table [trackBy]="trackByFn" [dataSource]="commonDataSource">

Eh bien, mais nous n'avons pas une telle condition que les données de la table changent d'une manière ou d'une autre, et ce n'est pas le sujet de la conversation maintenant. Mais que se passe-t-il si nous écrivons Pipe, qui, lors de l'initialisation de la source de données, cassera les données et les donnera à la table par portions. À son tour, la fonction trackBy aidera à éviter un rendu complet.

@Pipe({
  name: 'splitSchedule'
})
export class SplitPipe implements PipeTransform {
  public transform(value: any, takeBy: number = 4, throttleTime: number = 40): Observable<Array<any>> {
    return Array.isArray(value)
      ? this.getSplittedThread(value, takeBy, throttleTime)
      : of(value);
  }

  private getSplittedThread(data: Array<any>, takeBy: number, throttleTime: number): Observable<Array<any>> {
    const repeatNumber = Math.ceil(data.length / takeBy);
    return timer(0, throttleTime).pipe(
      map((current) => data.slice(0, takeBy * ++current)),
      take(repeatNumber)
    );
  }
}

Voici un si petit morceau de code qui aidera votre matériel à rendre de si grandes tables.

Appliquez ce tuyau à notre source de données.


<mat-table [trackBy]="trackByFn"
           [dataSource]="commonDataSource | splitSchedule | async">

Essayons maintenant de prendre des mesures. Rendu 100, 300 et 1000 éléments.







Et que voyons-nous? Ce qui est vraiment le succès n'est pas ce que nous attendions:

  • 300 éléments rendus 1 seconde plus vite
  • 1000 rendus en 11 secondes et l'onglet n'est pas mort
  • et 100 éléments rendus généralement 150 ms de plus

Mais ne vous précipitez pas pour tirer des conclusions, examinons d'abord le comportement de la page dans les deux cas.





Comme vous pouvez le voir, dans le cas habituel, la page se fige pendant quelques secondes et l'utilisateur ne peut rien faire à ce moment, alors que l'utilisation de notre pipe avec un lien vers trackBy donne à l'utilisateur une initialisation presque instantanée de la table et ne cause aucun inconfort lors de l'utilisation de l'application.

J'espère que cet article aide quelqu'un.

Le code source de l'application de test se trouve sur Stack Blitz .

All Articles