この記事はAngular Advent Calendar 2017の7日目の記事です。
Webブラウザの高速化とマシンスペックの向上により、Webアプリでも非常に大きなデータを扱う機会が増えてきました。
しかしながら、大量のデータを1ページ内に表示すると、目的のデータにたどり着くまでにスクロールを繰り返さないとならず、操作性と一覧性が悪くなってしまいます。
この課題を解決する手段として、ページング(ページャー)がよく用いられます。ページングを利用すると、1ページ内のデータ量を適切なサイズに保ちつつ、前後のデータを表示しやすくなります。
この記事では、Angularアプリでページングコンポーネントを作成して、一覧表(テーブル)を分割表示する方法を紹介します。
ページングコンポーネントのテンプレート
はじめに、ページングコンポーネントの完成イメージを示します。
ページングコンポーネントは、左から次の5個のボタンを備えます。
- 先頭ページへ移動
- 前のページへ移動
- ページ番号とページ数の表示領域(表示専用)
- 次のページへ移動
- 最終ページへ移動
テンプレートのコードは次の通りです。
<div class="btn-group" role="group"> <button class="btn btn-secondary" [disabled]="!canMoveToPreviousPage" (click)="moveToFirstPage()"> <i class="fa fa-angle-double-left"></i> </button> <button class="btn btn-secondary" [disabled]="!canMoveToPreviousPage" (click)="moveToPreviousPage()"> <i class="fa fa-angle-left"></i> </button> <button class="btn btn-secondary" disabled style="width:100px"> {{pageIndex + 1}} / {{_pageCount}} </button> <button class="btn btn-secondary" [disabled]="!canMoveToNextPage" (click)="moveToNextPage()"> <i class="fa fa-angle-right"></i> </button> <button class="btn btn-secondary" [disabled]="!canMoveToNextPage" (click)="moveToLastPage()"> <i class="fa fa-angle-double-right"></i> </button> </div>
ページングコンポーネントのプロパティ
コンポーネントのプロパティを定義します。
itemCount
: 項目数pageSize
: 1ページに表示する項目数_pageCount
: ページ数
itemCount
とpageSize
は、ページングコンポーネントの外部から設定する必要があるため、プロパティバインディングに対応させます。
_pageCount
は、前者2つのプロパティから値が決定されるプロパティであり、各setterメソッド内で値を計算します。このプロパティは外部から設定されることはありません。
@Component({ selector: 'pager', inputs: [ 'itemCount', 'pageSize' ], }) export class PagerComponent { constructor() { this.pageSize = 1; } _itemCount: number; get itemCount() { return this._itemCount; } set itemCount(value) { this._itemCount = value; this.updatePageCount(); } _pageSize: number; get pageSize() { return this._pageSize; } set pageSize(value) { this._pageSize = value; this.updatePageCount(); } _pageCount: number; updatePageCount() { this._pageCount = Math.ceil(this.itemCount / this.pageSize); } }
続いて、ページ番号を示すpageIndex
プロパティを定義します。ボタン操作により変更された値を外部に通知する必要がありますので、双方向バインディングに対応させます。
@Component({ selector: 'pager', inputs: [ 'pageIndex' ], outputs: [ 'pageIndexChange' ], }) export class PagerComponent { _pageIndex: number; pageIndexChange = new EventEmitter(); get pageIndex() { return this._pageIndex; } set pageIndex(value) { this._pageIndex = value; this.pageIndexChange.emit(this.pageIndex); } }
ページングコンポーネントのメソッド
ページ移動が可能かどうかを示すgetterメソッドを定義します。これらのメソッドは、ページ移動ボタンのdisabled
属性で使用され、ページ移動できない場合にボタン操作を無効にします。また、ページ移動メソッド内部でも使用されます。
export class PagerComponent { get canMoveToNextPage() : boolean { return this.pageIndex < this._pageCount - 1 ? true : false; } get canMoveToPreviousPage() : boolean { return this.pageIndex >= 1 ? true : false; } }
続いて、ページを移動するためのメソッドを定義します。これらのメソッドは、ページ移動ボタンのclick
イベントで実行されます。
export class PagerComponent { moveToNextPage() { if (this.canMoveToNextPage) { this.pageIndex++; } } moveToPreviousPage() { if (this.canMoveToPreviousPage) { this.pageIndex--; } } moveToLastPage() { this.pageIndex = this._pageCount - 1; } moveToFirstPage() { this.pageIndex = 0; } }
以上でページングコンポーネントは完成です。
メインページの実装
続いて、メインページのコンポーネントを作成します。
<tr>
要素のngFor
ディレクティブでSlicePipeを使用して、データの開始位置と終了位置を指定します。これにより、データを分割して一覧表に表示することができます。
<tr *ngFor="let item of items | slice:pageIndex*pageSize:(pageIndex+1)*pageSize"> <td *ngFor="let name of fieldNames">{{item[name]}}</td> </tr>
また、ページングコンポーネントを配置して、ページ操作に必要なプロパティとデータを定義します。
<pager [itemCount]="100" [(pageIndex)]="pageIndex" [pageSize]="pageSize"> </pager>
export class AppComponent { items: object[]; fieldNames: string[]; pageIndex: number; pageSize: number; constructor(appService: AppService) { this.items = appService.getOrders(100); this.fieldNames = [ 'No', '商品名', '受注日', '金額' ]; this.pageIndex = 0; this.pageSize = 10; } }
完成!
アプリ全体のソースと動作は次のサンプルで確認できます。
ページ移動ボタンをクリックして、次のページのデータが表示されることを確認してください。
Wijmo(ウィジモ)
最後に弊社製品の宣伝です。
JavaScript UIライブラリ「Wijmo(ウィジモ)」では、Angular用のページングコンポーネントを収録しています。また、ページング以外にも、ソート、フィルタリング、グループ化などのデータ操作に対応しています。
下記にWijmoのページングコンポーネントを用いたサンプルを示します。メインページの実装はほぼ同じで、ページングコンポーネントを独自に実装することなく、ページング機能を実現しています。
明日8日目は@daikiojmさんです。よろしくお願いします。