3 способа визуализации больших списков в Angular


Мы рассмотрим следующие техники: виртуальная прокрутка (с использованием Angular CDK), ручной рендеринг и прогрессивный рендеринг. Несмотря на то, что для их реализации мы будем использовать Angular, они также применимы и к другим фреймворкам.

1. Виртуальная прокрутка

Виртуальная прокрутка — наиболее эффективный способ обработки больших списков. Благодаря Angular CDK и другим плагинам ее можно с легкостью реализовать в любом компоненте.

Концепция проста:

  • элемент отображается только в том случае, если он находится в пределах видимых границ контейнера.

Чтобы использовать модуль прокрутки CDK, для начала нужно его установить:

npm i @angular/cdk

Затем импортируем модуль:

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

Теперь виртуальную прокрутку можно использовать в компонентах:

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

Несмотря на простоту реализации, результаты впечатляют. Компонент с легкостью отображает тысячи элементов.

Если виртуальная прокрутка настолько хороша и проста в реализации, то зачем использовать другие методы? На это есть несколько причин:

  • Результат сильно зависит от реализации: трудно управлять всеми возможными сценариями с помощью одной единственной реализации. Чем сложнее элемент, тем сложнее предсказать результат.
  • Модуль — это еще один большой кусок кода, добавленный в приложение.
  • Доступность и практичность: скрытые элементы не отображаются и, следовательно, не доступны для поиска.

Виртуальная прокрутка — отличный вариант (если она работает) при наличии:

  • неопределенного и, возможно, огромного списка элементов (предположительно больше 5 Кб, однако зависит от сложности каждого элемента);
  • бесконечной прокрутки элементов.

2. Ручной рендеринг

Один из вариантов ускорения рендеринга большого списка элементов — это ручной рендеринг с использованием API Angular, вместо *ngFor.

Возьмем простой шаблон цикла 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>

Для расчета рендеринга 10000 простых элементов мы будем использовать бенчмарк, вдохновленный js-frameworks-benchmark.

Первый тест выполнен с помощью *ngFor. Результаты: на скриптинг потребовалось 1099 мс, на рендеринг — 1553 мс, а на покрас — 3 мс.

С помощью 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>

Код контроллера изменяется следующим образом:

  • объявляем шаблон и контейнер
@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef; @ViewChild('item', { read: TemplateRef }) template: TemplateRef<any>;
  • после создания данных визуализируем их с помощью метода ViewContainerRef createEmbeddedViewwhen:
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 }); } }

Результаты показывают небольшое улучшение:

  • Скриптинг — 734 мс, рендеринг — 1443 мс и покрас — 2 мс:

Тем не менее на практике этот процесс все еще недостаточно быстрый, поскольку при нажатии кнопки браузер зависает на несколько секунд.

Процесс выглядит следующим образом (движения мышь имитируют индикатор загрузки ????):

Теперь перейдем к прогрессивному рендерингу в сочетании с ручным.

3. Прогрессивный рендеринг

Концепция прогрессивного рендеринга заключается в том, чтобы визуализировать поднабор элементов и отложить рендеринг других элементов в цикле событий, что позволяет браузеру плавно и последовательно отображать все элементы.

Код очень простой:

  • мы создаем интервал каждые 10 мс и отображаем 500 элементов одновременно;
  • когда все элементы будут отображены согласно индексу, мы останавливаем интервал и прерываем цикл.
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);

Обратите внимание, что количество отображаемых элементов и время интервала полностью зависят от конкретного случая. Например, при наличии большого количества сложных элементов рендеринг 500 из них будет очень медленным.

Как видите, статистика выглядит хуже:

Однако увеличение времени рендеринга остается незаметным для пользователя, поскольку 500 элементов отображаются одновременно, а сам процесс происходит за пределами контейнера.

При изменении размера контейнера или положения прокрутки в некоторых случаях могут возникнуть проблемы, которые необходимо устранить.

Процесс выглядит следующим образом:

Заключение

Вышеуказанные методы будут полезны, если виртуальная прокрутка оказывается не лучшим вариантом.

Однако во многих случаях виртуальная прокрутка с использованием такой великолепной библиотеки, как Angular CDK, является лучшим способом работы с большими списками.


Перевод статьи Giancarlo Buomprisco: 3 Ways to Render Large Lists in Angular


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.