K4750.NET

Angular Material 7 で Virtual Scrolling を試してみる

Angular6.1で遊んでいたらAngular7がリリースされたので、Virtual Scrollingでちょっと遊んでみる。


1.Angular CLIのアップデート

まずは・・・先日Angular CLIを6.2.5へアップデートしたばかりだが、Angular CLIも7.0.2がリリースされていたのでアップデートする。

$ npm uninstall -g @angular/cli
$ npm cache verify
$ npm install -g @angular/cli@7.0.2

念のためバージョンを確認する。

$ ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 7.0.2
Node: 8.12.0
OS: win32 x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.10.2
@angular-devkit/core         7.0.2
@angular-devkit/schematics   7.0.2
@schematics/angular          7.0.2
@schematics/update           0.10.2
rxjs                         6.3.3
typescript                   3.1.3

2.Angular7でプロジェクトを新規作成する

Angular CLI 7.X で ng new を実行すると、routingの使用やスタイルシートのフォーマットについて聞かれる。

$ ng new virtualscrolling
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]
CREATE virtualscrolling/angular.json (3949 bytes)
CREATE virtualscrolling/package.json (1323 bytes)
CREATE virtualscrolling/README.md (1033 bytes)
...

プロジェクトができたところで、Angularのバージョンを確認してみる。

$ cd virtualscrolling
$ ng version
...
Angular CLI: 7.0.2
Node: 8.12.0
OS: win32 x64
Angular: 7.0.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router
...

無事 Angular 7.0.0 がインストールされた。


3.Angular Materialインストール

次は Angular Material 7.X をインストールする。

$ npm install --save @angular/material @angular/cdk @angular/animations
...
+ @angular/animations@7.0.0
+ @angular/material@7.0.0
+ @angular/cdk@7.0.0
...

インストールが終わったら、公式サイトの「Getting started | Angular Material」に従い、

Step 2: Configure animations
Step 4: Include a theme

を作成したプロジェクトに適用しておく(詳細は割愛)。他の手順は必要に応じて後でやる。さて、ここまでで一旦動かしてみる。

$ ng serve --open

問題なく「Welcome to virtualscrolling!」が表示される。


3.データの準備

とりあえず、そこそこの量のデータを・・・ということで、app.component.ts の codes 配列に、Unicode文字を0x0000 ~ 0x9999の1万文字用意する。

import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'virtualscrolling';
  codes = [];

  ngOnInit() {
    for (let i = 0x0000; i < 0x9999; i++) {
      this.codes.push({
        code: '0x' + ('0000' + i.toString(16).toLowerCase()).substr(-4),
        char: String.fromCharCode(i)
      });
    }
  }
}

4.Virtual Scrolling

まずは、app.component.html を以下のようにして、1万文字をそのままループで表示してみる。

<div *ngFor="let i of codes">
  {{i.code}} - {{i.char}}
</div>

表示にかかる時間を計測すると、約9秒程度。
次に、Virtual Scrollingを試してみる。app.module.ts へ ScrollDispatchModule を追加して、

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ScrollDispatchModule } from '@angular/cdk/scrolling';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ScrollDispatchModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.scss を(本家のサンプルをそのままに)以下とし、

.example-viewport {
  height: 200px;
  width: 200px;
  border: 1px solid black;
}

.example-item {
  height: 50px;
}

app.component.html を(こちらも本家のサンプルを参考に)以下とする。

<cdk-virtual-scroll-viewport itemSize="50" class="example-viewport">
  <div *cdkVirtualFor="let i of codes" class="example-item">{{i.code}} - {{i.char}}</div>
</cdk-virtual-scroll-viewport>

表示されるまでの時間は1秒以下(厳密には計測してないが、そのほとんどはAngularの初期化にかかる時間だろう)。DOMの状態をDevToolsで確認してみると、

<cdk-virtual-scroll-viewport _ngcontent-c0="" class="example-viewport cdk-virtual-scroll-viewport cdk-virtual-scroll-orientation-vertical" itemsize="50" ng-reflect-item-size="50">
  <div class="cdk-virtual-scroll-content-wrapper" style="transform: translateY(896950px);">
    <!--bindings={
      "ng-reflect-cdk-virtual-for-of": "[object Object],[object Object"
    }-->
    <div _ngcontent-c0="" class="example-item">0x4613 - 䘓</div>
    <div _ngcontent-c0="" class="example-item">0x4614 - 䘔</div>
    <div _ngcontent-c0="" class="example-item">0x4615 - 䘕</div>
    <div _ngcontent-c0="" class="example-item">0x4616 - 䘖</div>
    <div _ngcontent-c0="" class="example-item">0x4617 - 䘗</div>
    <div _ngcontent-c0="" class="example-item">0x4618 - 䘘</div>
    <div _ngcontent-c0="" class="example-item">0x4619 - 䘙</div>
    <div _ngcontent-c0="" class="example-item">0x461a - 䘚</div>
    <div _ngcontent-c0="" class="example-item">0x461b - 䘛</div>
    <div _ngcontent-c0="" class="example-item">0x461c - 䘜</div>
    <div _ngcontent-c0="" class="example-item">0x461d - 䘝</div>
  </div>
  <div class="cdk-virtual-scroll-spacer" style="transform: scaleY(1.96605e+06);"></div>
</cdk-virtual-scroll-viewport>

スクロールバーの操作に応じて、div.cdk-virtual-scroll-content-wrapper の内容が一定量を超えないよう動的に変化する動きをしていた。また、スクロールバーを正しく表示させるために、div.cdk-virtual-scroll-content-wrapper の translateY と、div.cdk-virtual-scroll-spacer の scaleY を変化させて表示行の上下に巨大な空白を作り出していた・・・なるほど、そういう仕組みなのね!