はじめに
フロントエンドエンジニアの有馬です。
弊社のプロダクトのひとつにアンケートシステムがあります。そこにはアンケートの回答データを閲覧する機能がありますが、多くのユーザーが回答するとこのデータ量が膨大になることがあります。まずは何も対策せずに Angular Material の Table で表示してみることにしましたが、表示に時間がかかりユーザビリティを損ねると判断し、仮想スクロールで対応することを検討し始めました。
しかし、Angular Material の Table だと仮想スクロールを実装するのがかなり大変だったので、別の選択肢を探していたところ AG Grid というライブラリを見つけました。採用を検討するに当たって Angular Material の Table と AG Grid にどれくらいパフォーマンスに差があるか検証したので、今回はその結果をお届けします。
検証方法
以下の方法で 1,000・100,000・1,000,000 行のデータを表示した時のレンダリング時間を比較します。
- 仮想スクロール未対応の Angular Material Table
- 仮想スクロール対応済みの Angular Material Table
- AG Grid(デフォルトで仮想スクロール対応されています)
今回はどのパターンも以下のデータを使って検証しました。
// element-data.ts export interface PeriodicElement { name: string; position: number; weight: number; symbol: string; description: string; } const data: PeriodicElement[] = [ // 10 個のデータ ] export const ELEMENT_DATA: PeriodicElement[] = Array.from({ length: 10, // ここで表示するデータ数を編集することができます。 }).flatMap((_, i) => { return data.map((d) => { return { ...d, position: d.position + data.length * i, }; }); });
レンダリング時間の計測は次のようにおこなっています(もっと良い方法があればコメント欄などで教えてください🙏)。 モード切り替え後の初回レンダリングが長めに計測されてしまうとい う不具合があり、検証結果にはモード切り替え後の初回レンダリング以降に3回計測した結果を載せています。
// 検証対象の各コンポーネントに記述 renderStart = signal<DOMHighResTimeStamp>(0); renderEnd = signal<DOMHighResTimeStamp>(0); renderTime = signal<DOMHighResTimeStamp>(0); ngOnInit(): void { this.renderStart.set(performance.now()); // コンポーネントが初期化された時 } constructor() { afterNextRender(() => { // 全てのレンダリングが完了した時 this.renderEnd.set(performance.now()); this.renderTime.set(this.renderEnd() - this.renderStart()); }); }
<p>レンダリング時間: {{renderTime()}} ms</p>
以下が検証に使用した実装例です。
検証結果
仮想スクロール未対応の Angular Material Table
行数 | レンダリング時間 |
---|---|
1,000 | 47~54ms |
100,000 | 3,800~3,942ms |
1,000,000 | 表示不可 |
1,000,000行だとstackBlitzがクラッシュしてしまい表示できませんでした。100,000行でもときどきクラッシュすることがありました。
仮想スクロール対応済みの Angular Material Table
行数 | レンダリング時間 |
---|---|
1,000 | 1.89~2ms |
100,000 | 1.79~2.09ms |
1,000,000 | 1.7~2.2ms |
行の数が増えてもレンダリング時間が変わることはなく、一番良いパフォーマンスとなりました。 しかし、高さが可変の行に対応できておらず、スクロール時にがたつきが起こってしまっています。 また、1,000,000 行を表示した際スクロールバーを一番下まで下げても47万行目付近でスクロールできなくなっていました。既知の問題らしく以下の issue が挙げられていました。(解決方法もコメントされていましたが結構手間がかかるため実装は割愛しています) https://github.com/angular/components/issues/14990
AG Grid
行数 | レンダリング時間 |
---|---|
1,000 | 16~24ms |
100,000 | 107~140ms |
1,000,000 | 838~960ms |
行数が多くなってくるとレンダリング時間に影響が出始めました。表示しているDOMは制限していてもなにか全体に対する処理を行っているのかもしれません。
それぞれのメリット・デメリット
結果を踏まえてそれぞれの手法のメリットデメリットをまとめてみました。
仮想スクロール未対応の Angular Material Table
メリット
- 特に対策が必要ない
デメリット
- 表示数に限界がある
おすすめのユースケース
- 1,000行くらいまでのデータの表示
1,000行ほどのデータであればどの手法でもレンダリング時間に人が認識できるほどの差はなく、未対策の Angular Material Table でも特に問題なさそうです。
仮想スクロール対応済みの Angular Material Table
メリット
- パフォーマンスが良い、行数がいくら多くなっても影響がない
デメリット
- 実装が大変
- 行の高さが可変だとスクロール時にちらつきがある
- ヘッダーを固定位置にするのが難しい
- 表示領域外のデータにはブラウザの検索機能が効かない
おすすめのユースケース
- 10,000行以上など多くの行を表示する必要があるとき
- 極限までパフォーマンスチューニングしたいとき
Angular Material Table に仮想スクロールの実装をするのが一番パフォーマンスがよかったのですが、高さが可変の行に対応したい、ヘッダーを固定表示したいなどの要望があったときにカスタマイズが大変です。パフォーマンスを特に気にする必要がある場合で基本的な表示で済む場合、もしくはカスタマイズの工数を多めにとっても問題ない場合は、 Angular Material Table での仮想スクロールを選択した方が良いでしょう。
AG Grid
メリット
- 実装が容易
- 高さが可変の行に対応した仮想スクロール
- 列ごとの検索など便利な機能が容易に実装できる
- 列幅の変更や列の入れ替えもデフォルトで実装されている
- 機能を追加したり、スタイルを変更しても仮想スクロールの調整をする必要がない
デメリット
- 行が極端に多くなってくると影響が出てくる
- 一部機能が有料
- テーブルUIのためにパッケージを追加する必要がある
おすすめのユースケース
- パフォーマンスがそこまで要求されない場合
- 列幅の変更や列の入れ替えなどユーザーによるインタラクティブな操作が求められる場合
- 高さが可変の行を表示する必要がある場合
AG Grid は仮想スクロール用の設定をすることなく基本的なテーブル表示をすれば仮想スクロール対応をしてくれるので実装はとても楽でした。
また、列ごとの検索や列の横幅の調整、CSVエクスポートなどテーブルUIに求められる機能は一通り用意されている印象です。
一方で、行のグループ表示やExcelエクスポートなどの有料ライセンスを購入しないと利用できない機能もあるのでその点は注意が必要でしょう。
おわりに
エモーションテックではアンケート回答結果に自由解答が含まれており高さが可変の行の表示が必要だったこと、今後の機能実装の容易さによる工数削減を見込んで AG Grid を使っていくことにしました。みなさんもユースケースに合わせて何を使うか選択してみてください。
エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。プロダクトに興味のある方、Angular を使ったアプリケーション開発をしたい方、ぜひ採用ページからご応募をお願いいたします。