3 maneiras de renderizar grandes listas em Angular

Em 2020, as estruturas de front-end se tornaram melhores, mais eficientes e mais rápidas. Mas, mesmo com isso em mente, renderizar grandes listas sem congelar o navegador ainda pode ser uma tarefa assustadora, mesmo para as estruturas mais rápidas existentes.

Este é um daqueles casos em que "a estrutura é rápida e o código é lento". Existem muitas abordagens que permitem exibir um grande número de elementos sem bloquear a interação do usuário com a página da web. O autor do artigo, cuja tradução estamos publicando hoje, deseja explorar os métodos existentes para exibir grandes listas em páginas da web e falar sobre suas áreas de aplicação.







Embora este material seja destinado ao Angular, o que está sendo discutido aqui se aplica a outras estruturas e projetos que são escritos em JavaScript puro. Em particular, as seguintes abordagens para renderizar grandes listas serão consideradas aqui:

  • Rolagem virtual (usando Angular CDK).
  • Renderização manual.
  • Renderização progressiva.

Rolagem virtual


A rolagem virtual é talvez a maneira mais eficiente de trabalhar com listas grandes. É verdade que ele tem algumas desvantagens. A rolagem virtual, graças ao Angular CDK e outros plugins, é muito fácil de implementar em qualquer componente.

A idéia por trás da rolagem virtual é simples, mas nem sempre fácil de implementar. A linha inferior aqui é que temos um contêiner e uma lista de elementos. Um elemento é renderizado apenas se estiver dentro da área visível do contêiner.

Usaremos o módulo scrollingdo Angular CDK, projetado para organizar a rolagem virtual. Para fazer isso, primeiro você precisa instalar o CDK:

npm i @angular/cdk

Então você precisa importar o módulo:

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

Depois disso, nos componentes você pode usar cdk-virtual-scroll-viewport:

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

Aqui está um exemplo de projeto que usa essa abordagem para organizar a rolagem virtual.

Como você pode ver, os mecanismos angulares padrão permitem, sem muita dificuldade, obter resultados impressionantes. O componente no exemplo renderiza muitos milhares de elementos sem problemas.

Se a rolagem virtual é tão boa e fácil de implementar, surge a questão de por que estudar outras maneiras de renderizar grandes listas. Eu também estava interessado nesta questão. Como se viu, esse estado de coisas tem vários motivos:

  • , , . , . , Autocomplete ( ). , , , , . — .
  • — , .
  • Existem alguns problemas de acessibilidade e facilidade de uso do conteúdo de uma lista com rolagem virtual. Os elementos ocultos não são renderizados - isso significa que eles não estarão disponíveis para os leitores de tela e que não poderão ser encontrados na página usando mecanismos padrão do navegador.

A rolagem virtual é ideal em várias situações (desde que funcione):

  • Nesse caso, se você deseja exibir listas cujo tamanho não é conhecido antecipadamente ou aquelas que podem ser enormes (de acordo com uma estimativa aproximada, listas que incluem mais de 5 mil elementos, mas isso depende muito da complexidade de cada elemento).
  • Caso você precise organizar a rolagem sem fim.

Renderização manual


Uma das maneiras de trabalhar com listas que tentei acelerar a saída de um grande conjunto de elementos é usar a renderização manual usando a API Angular *ngFor.

Temos um modelo simples que usa um loop organizado usando a diretiva *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>

Para medir o desempenho de renderização de 10.000 elementos simples, usei um benchmark baseado em js-frameworks-benchmark .

Primeiro, examinei o desempenho de uma lista, que usa um loop regular para a saída *ngFor. Como resultado, verificou-se que a execução do código (Scripting) levou 1099 ms., A renderização (Rendering) levou 1553 ms. E o desenho (Pintura) - 3 ms.


Um estudo do desempenho de uma lista, que usa * ngFor para produzir, os

itens da Lista podem ser renderizados manualmente usando a API Angular:

<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>

Vamos falar sobre como o código do controlador mudou.

Declaramos um modelo e um contêiner:

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

Ao gerar dados, nós os renderizamos usando o método de createEmbeddedViewentidade 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
    });
  }
}

Como resultado, os indicadores que caracterizam o desempenho da lista foram ligeiramente aprimorados. Ou seja, foram necessários 734 ms para executar o código, 1443 ms para renderizar e 2 ms para desenhar.


Pesquisando o desempenho de uma lista que usa a API Angular para gerar

True, na prática, isso significa que a lista ainda é extremamente lenta. Quando você clica no botão apropriado, o navegador "congela" por alguns segundos. Se isso aparecesse na produção, os usuários definitivamente não gostariam.

Aqui está o que parece (aqui eu, usando o mouse, imito o indicador de carregamento).


Lentidão na lista

Agora, para melhorar a renderização manual da lista, tentaremos aplicar a tecnologia de renderização progressiva.

Renderização progressiva


A idéia de renderização progressiva é simples. Consiste em renderizar um subconjunto de elementos progressivamente, adiando a renderização de outros elementos no loop de eventos. Isso permite que o navegador exiba todos os elementos sem "diminuir a velocidade".

O código abaixo que implementa a renderização progressiva da lista é muito direto:

  • Primeiro, usando isso setInterval, estabelecemos uma chamada regular a cada 10 ms para a função que renderiza 500 elementos quando chamada.
  • Depois de deduzidos todos os elementos, que determinamos com base na análise do índice do elemento atual, paramos a chamada de função regular e interrompemos o ciclo.

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

Observe que o número de elementos exibidos por chamada para a função que está sendo transferida setInterval, bem como a frequência da chamada para essa função, dependem completamente das circunstâncias. Por exemplo, se os elementos da lista exibida forem muito complexos, a saída de 500 desses elementos de uma só vez será inaceitavelmente lenta.

Ao medir o desempenho desta solução, obtive resultados que parecem piores do que aqueles que recebi anteriormente. Execução de código - 907 ms., Renderização - 2555 ms., Desenho - 16 ms.


Pesquisando o desempenho de uma lista, cuja saída usa renderização progressiva,

mas o usuário, trabalhando com essa lista, experimentará uma experiência muito mais agradável do que antes. Embora o tempo necessário para renderizar a lista tenha aumentado, o usuário não notará. De uma só vez, renderizamos 500 elementos. Nesse caso, a renderização é realizada fora dos limites do contêiner.

Aqui, alguns problemas podem surgir devido ao fato de o contêiner mudar de tamanho durante a renderização ou a posição da rolagem do conteúdo ser alterada. Se isso acontecer, você terá que lidar com problemas semelhantes.

Aqui está a aparência de trabalhar com essa lista.


A lista é rápida

Sumário


Técnicas de renderização manual e progressiva para listas grandes certamente são úteis em algumas situações. Eu os usei nos casos em que a rolagem virtual, por algum motivo, não era adequada para mim.

Dado o exposto, podemos dizer que, na maioria das vezes, a rolagem virtual, construída com base em uma boa biblioteca, como o Angular CDK, é a melhor maneira de exibir grandes listas. Mas se a rolagem virtual não puder ser usada por algum motivo, o desenvolvedor terá outras possibilidades.

Queridos leitores! Como você produz grandes listas em seus projetos da web?


All Articles