Acelere el renderizado de MatTable en pocos pasos

A menudo sucede que la mayor parte de la aplicación está compuesta por varias listas y tablas. Para no reinventar la rueda cada vez, yo, como muchos, solía usar tablas de material angular .

Más tarde, estas tablas crecieron, tanto los datos de texto como los elementos anidados se colocaron en celdas. Todo esto crece y se convierte en una buena carga en la máquina del usuario. Y, por supuesto, a nadie le gusta esto.

En mi último proyecto de casa, había una tabla cuyas celdas estaban en su mayoría llenas de varios campos (incluso se podría decir que era una gran forma).

Y el renderizado tardó unos 8 segundos (tabla de 40 x 40).

Entonces, ¿cómo puede optimizar MatTable para listas grandes?

imagen

Caso de prueba


Para ayudar a otros a lidiar con este problema, escribí una pequeña aplicación de prueba. La misma tabla de MatTable, con cinco columnas (la primera es la identificación del elemento, el resto son campos de texto regulares de MatFormField).



Parecería que es una tabla simple, pero incluso hacer que 100 sea el drenaje de una tabla así toma ni más ni menos de 2 segundos (Oh, este material y su rendimiento "superior").



El renderizado en este caso solo tomó (860 ms), sin embargo, la página se congeló durante 2.2 segundos y el usuario estaba molesto.

Bueno, intentemos renderizar una tabla con 300 filas. Y en el redoble de batería, estamos esperando un poco y vemos que se gastaron casi 10 segundos en secuencias de comandos y renderizado en total. Es decir, en el caso de que el usuario quiera mostrar dicha tabla en 300 filas, su página se congelará durante 10 segundos. Esto es muy aterrador y sorprendente (en realidad no).



De hecho, todavía estaba tratando de medir el tiempo que tomaría dibujar 1000 elementos, pero mi patético i7 no podía soportarlo y la página se caía constantemente.
Intentaremos hacer esto más tarde con la solución ya aplicada.

Solución al problema


Y así, todos pueden tomar medidas utilizando utilidades basadas en navegador, pero no hay solución para este problema.

Esta decisión me llegó al razonar sobre la esencia misma del problema.
Una correcta comprensión del problema ya es al menos la mitad de su solución.
Creo que todos entienden que esto está sucediendo porque los datos de la tabla primero se procesan mediante scripts y luego se escupen en una sola pieza . A su vez, esta es precisamente la razón por la que vemos una suspensión característica.

Problema encontrado Ahora queda por descubrir cómo resolverlo. La primera solución viene a la mente. Si el problema es procesar todos los datos a la vez, entonces debe hacer que la tabla procese los datos en partes.

¿Pero cómo hacer eso?

Pensemos qué tenemos para esto fuera de la caja. Lo primero que viene a la mente es la función Track By. Al cambiar el origen de datos, no se volverá a representar toda la tabla, sino solo sus cambios.

Agreguemos esta función a nuestra tabla:

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

Bueno, pero no tenemos la condición de que los datos de la tabla cambien de alguna manera, y de esto no se trata ahora la conversación. Pero, ¿qué pasa si escribimos Pipe, que, al inicializar la fuente de datos, dividirá los datos y los entregará a la tabla en lotes. A su vez, la función trackBy ayudará a evitar un renderizador completo.

@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)
    );
  }
}

Aquí hay un código tan pequeño que ayudará a su hardware a generar tablas tan grandes.

Aplique esta tubería a nuestra fuente de datos.


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

Ahora intentemos tomar medidas. Renderiza 100, 300 y 1000 elementos.







Y ¿qué vemos? Lo que realmente éxito no es lo que esperábamos:

  • 300 elementos procesados ​​1 segundo más rápido
  • 1000 prestados en 11 segundos y la pestaña no murió
  • y 100 elementos generalmente renderizados 150 ms más

Pero no se apresure a sacar conclusiones, primero veamos el comportamiento de la página en ambos casos.





Como puede ver, en el caso habitual, la página se congela durante unos segundos y el usuario no puede hacer nada en este momento, mientras usa nuestra tubería con un enlace para rastrear, le da al usuario una inicialización de tabla casi instantánea y no causa ninguna molestia mientras usa la aplicación.

Espero que este artículo ayude a alguien.

El código fuente de la aplicación de prueba está en Stack Blitz .

All Articles