Angular CDK を使用して大規模なリストを仮想化する

仮想スクロールを実装して、大きなリストの応答性を高めます。

Stephen Fluin
Stephen Fluin

スクロール リストは、お気に入りのソーシャル メディア サイトの無限スクロール フィードのブラウジングやエンタープライズ ダッシュボードのナビゲーションなど、現在最も一般的な UI パターンの一つです。スクロール リストが非常に長くなると(数十万、数千、または数十万のアイテム)、アプリケーションのパフォーマンスが低下する可能性があります。

大規模なリストでは、事前にすべてのデータを読み込んでレンダリングする必要があるため、読み込みに時間がかかることがあります。また、リスト内の各アイテムにはリッチデータ、メディア、機能が含まれるため、レンダリングと操作が遅くなることもあります。

ページの読み込みまたはスクロール時に問題が発生すると、ユーザーの不満やページの放棄につながる可能性があります。

コンポーネント開発キットを使用した Angular での仮想スクロール

仮想スクロールは、このようなスケーリングの問題に対処するための主要な手法です。仮想スクロールは、適切なサイズのスクロールバーを提供することで、非常に大きなリストのような印象を与えます。また、アプリケーションがリスト全体をメモリ内に保持したり、ページ上にレンダリングしたりしなくても、リストをナビゲーションできるようになります。

Angular では、仮想スクロールはコンポーネント開発キット(CDK)によって提供されます。リストを反復処理する方法を変更し、いくつかの追加構成パラメータを指定することで、CDK の仮想スクロールがリストの仮想レンダリングを自動的に管理し、ページのパフォーマンスと応答性を向上させます。

リスト全体を一度にレンダリングするのではなく、画面に収まるアイテムのサブセット(および小さなバッファ)のみをレンダリングします。ユーザーが移動すると、アイテムの新しいサブセットが計算されてレンダリングされ、必要に応じて既存の DOM が再利用されます。

この投稿の残りの部分では、基本的な仮想スクロールの設定方法について説明します。このサンプルアプリで、完全に機能する例を確認できます。

仮想スクロールの設定

まず、任意のパッケージ管理システムを使用して @angular/cdk がインストールされていることを確認します。npm を使用してインストールするには、ターミナルで次のコマンドを実行します。

npm install --save @angular/cdk

アプリに ScrollingModule を追加する

CDK をインストールしたら、仮想スクロールを処理する ScrollingModule@angular/cdk/scrolling パッケージからインポートします。次に、これをモジュールの imports 配列に追加します。

import {ScrollingModule} from '@angular/cdk/scrolling';

...
imports: [
  ScrollingModule
...
]
...

ビューポートを作成する

パッケージの動作を確認するために、0 から 99,999 までの数字からなる簡単なリストを使ってコンポーネントを作成してみましょう。

@Component({
  template: `<div *ngFor="let item of list">{{item}}</div>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

ブラウザはアプリをレンダリングする際に、100,000 個の個別の <div> 要素をレンダリングする必要があります。単純なテキストノードであればこれで十分かもしれませんが、繰り返しテンプレートが複雑になっても、スケーリングが難しくなり、イベント リスナーが大幅に増えることになります。

仮想スクロールを追加して問題を回避するには、リストを <cdk-virtual-scroll-viewport> 要素でラップしてビューポートを作成する必要があります。

@Component({
  template: `<cdk-virtual-scroll-viewport>
    <div *ngFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

ScrollingModule はリストのサブセットを動的にレンダリングするため、標準の CSS を使用してビューポートの高さを指定する必要があります。また、itemSize を指定して、ビューポートにコンテンツに関するヒントを付与する必要もあります。モジュールはこの情報を使用して、ある時点で DOM に保持するアイテムの数と、適切なサイズのスクロールバーをレンダリングする方法を決定します。

@Component({
  template: `<cdk-virtual-scroll-viewport itemSize="18" style="height:80vh">
    <div *ngFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

最後に、*ngFor*cdkVirtualFor に変換します。

@Component({
  template: `<cdk-virtual-scroll-viewport itemSize="18" style="height:80vh">
    <div *cdkVirtualFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

リスト全体を反復処理するのではなく、ビューポートが、ユーザーのリストの正しいサブセットを動的に識別して反復処理します。これで、ユーザーがページを読み込むと、CDK は画面に収まるリストのサブセット(および少量のバッファ)をレンダリングし、ビューポートのスクロール イベントがあれば、リストの適切なサブセットを読み込んでレンダリングします。

ユーザーがスクロールしたときのリストの CDK レンダリング サブセット。

さらに先へ

CDK の仮想スクロール機能は、この基本的な例よりもはるかに優れています。サンプルアプリでは、リスト全体がメモリ内にありますが、より複雑なアプリケーションでは、オンデマンドでリストを取得できました。ScrollingModulecdkVirtualOf ディレクティブのその他の機能について詳しくは、CDK ドキュメントScrolling をご覧ください。

ヒーロー画像(投稿: Mr Cup / Fabien Barral、Unsplash