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

仮想スクロールを実装して、大きなリストのレスポンスを向上させます。

Stephen Fluin
Stephen Fluin

スクロール リストは、お気に入りのソーシャル メディア サイトで無限スクロール フィードを閲覧する場合でも、エンタープライズ ダッシュボードを操作する場合でも、現在最も一般的な UI パターンの 1 つです。スクロール リストが非常に長くなると(数百、数千、数十万のアイテム)、アプリのパフォーマンスが低下する可能性があります。

大きなリストは、アプリケーションがすべてのデータを事前に読み込んでレンダリングする必要があるため、読み込みに時間がかかることがあります。また、リスト内の各アイテムにリッチデータ、メディア、機能が含まれる可能性があるため、レンダリングやナビゲーションが遅くなることもあります。

ユーザーはページの読み込みやスクロールで問題に直面し、不満を感じてページを離脱する可能性があります。

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