Мы рассмотрим следующие техники: виртуальная прокрутка (с использованием 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, является лучшим способом работы с большими списками.
Читайте также:
- Из двух модулей Angular выбирай лучший- задачка для программиста
- Объединение Google API с Angular
- Повесть об однонаправленном потоке данных в Angular
Перевод статьи Giancarlo Buomprisco: 3 Ways to Render Large Lists in Angular