Open
16

【Angular Material】テーブルの使いかた

公式ドキュメントを翻訳にかけて、あとから推敲していく。
formArrayとの組み合わせ方の最適解を探りたい。
環境構築はスキップ。

import {MatTableModule} from '@angular/material/table';

何に使う

データ行の表示に、Material Designを適用することができる。
CDK data-tableを基に作られているため、実装方法はCDKのドキュメントを参考にする。
要素や属性の前のprefixを"cdk-"ではなく"mat-"とする。

  1. mat-tableの記述とデータの提供
    まず、<table mat-table>コンポーネントをテンプレートに追加し、データを渡します。

テーブルにデータを提供する最も簡単な方法は、テーブルの dataSource 入力にデータ配列を渡すことです。テーブルは、配列を受け取り、データ配列内の各オブジェクトの行をレンダリングします。

<table mat-table [dataSource]="myDataArray">
...
</table>。
テーブルはパフォーマンスを最適化するため、データ配列への変更を自動的にチェックしません。その代わり、データ配列にオブジェクトが追加されたり、削除されたり、移動されたりすると、テーブルのrenderRows()メソッドを呼び出して、テーブルのレンダリングされた行の更新をトリガーすることができます。

配列は、データソースにデータをバインドする最もシンプルな方法ですが、最も制限の多い方法でもあります。より複雑なアプリケーションでは、データソースのインスタンスを使用することをお勧めします。詳細は、「高度なデータソース」の項を参照してください。

  1. 列テンプレートの定義
    次に、テーブルの列テンプレートを作成します。

各列の定義には固有の名前を付け、ヘッダーと行のセルの内容を記述します。

ここでは、「score」という名前のシンプルな列定義を示します。ヘッダーセルには「Score」というテキストが入り、各行セルには各行のデータの score プロパティが表示されます。

<ng-container matColumnDef="score">
<th mat-header-cell *matHeaderCellDef> score </th>
<td mat-cell *matCellDef="let user"> {{user.score}}。</td> </td
</ng-container>
なお、セルテンプレートは、単純な文字列値の表示のみに限定されているわけではなく、任意のテンプレートを提供できる柔軟性があります。

カラムがヘッダーとセルに単一の文字列値をレンダリングするだけの役割を果たす場合は、代わりに mat-text-column を使用してカラムを定義することができます。以下のカラム定義は、上記のものと同等です。

<mat-text-column name="score"></mat-text-column>。
ヘッダーテキスト、テキストの配置、セルデータアクセッサをどのようにカスタマイズできるかについては、mat-text-column の API ドキュメントとサンプルをご覧ください。なお、flex-layout テーブルとは互換性がありません。また、データのプロパティが最小化される可能性がある場合は、データアクセサを提供する必要があります。最小化後は文字列名が一致しなくなるためです。

  1. 行テンプレートの定義
    最後に、列を定義したら、ヘッダー行とデータ行でどの列をレンダリングするかをテーブルに伝える必要があります。

まず、レンダリングしたい列のリストを含む変数をコンポーネント内に作成します。

columnsToDisplay = ['userName', 'age'];
次に、mat-table のコンテンツに mat-header-row と mat-row を追加し、入力として列のリストを指定します。

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>。
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>。
なお、行に提供される列のリストは、列定義を書いた順番に限らず、どのような順番でも構いません。また、テンプレートで定義したすべての列を必ずしも含める必要はありません。

つまり、行に提供されるカラムリストを変更することで、簡単にカラムの順序を変更したり、カラムを含む/含まないを動的に変更することができます。

高度なデータソース
テーブルにデータを提供する最も簡単な方法は、データ配列を渡すことです。より複雑なケースでは、Observable ストリームを使った柔軟なアプローチや、データソースのロジックを DataSource クラスにカプセル化する方法が有効です。

データ配列のObservableストリーム
テーブルにデータを提供する別のアプローチとして、データ配列が変更されるたびにレンダリングされるようにエミットするObservableストリームを渡す方法があります。テーブルはこのストリームをリッスンし、新しいデータ配列が出力されるたびに、自動的に行の更新を行います。

データソース
ほとんどの実世界のアプリケーションでは、テーブルにDataSourceインスタンスを提供することが、データを管理するための最良の方法です。DataSourceは、アプリケーションに特有のソート、フィルタリング、ページング、データ検索のロジックをカプセル化する場所として機能します。

DataSourceは、最低でもconnectとdisconnectの2つのメソッドを持つクラスです。connectメソッドは、レンダリングされるべきデータ配列をエミットするObservableを提供するためにテーブルから呼び出されます。テーブルが破壊されたときに disconnect が呼び出されますが、これは connect メソッドに登録されている可能性のあるサブスクリプションをクリーンアップするのに適したタイミングかもしれません。

Angular Material は MatTableDataSource という既製のテーブルデータソースクラスを提供していますが、より複雑なユースケースのために独自のカスタムデータソースクラスを作成したいと思うかもしれません。これは、抽象DataSourceクラスをカスタムDataSourceクラスで拡張し、connectメソッドとdisconnectメソッドを実装することで可能になります。カスタムDataSourceが別のベースクラスを拡張して機能を継承しなければならないユースケースでは、1つのベースクラスのみを実装するというTypescriptの制限を尊重するために、DataSourceのベースクラスを代わりに実装することができます(MyCustomDataSource extends SomeOtherBaseClass implements DataSource)。

特徴
MatTable は単一の責任に焦点を当てています: 効率的にデータ行をパフォーマンスとアクセス性の高い方法でレンダリングすることです。

テーブル自体は多くの機能を備えていませんが,その機能を満たすコンポーネントの構成にテーブルが含まれることを期待していることに気づくでしょう.

例えば,MatSort や MatPaginator を使い,それらの出力に応じてテーブルに提供されるデータを変更することで,テーブルにソートやページネーションを追加することができます.

データの配列をソート、ページ分割、フィルタリングできるテーブルを持つというユースケースを単純化するために、Angular Material ライブラリには、現在のテーブルの状態に応じてどの行をレンダリングすべきかを決定するロジックが既に実装されている MatTableDataSource が付属しています。これらの機能をテーブルに追加するには、以下のそれぞれのセクションをご覧ください。

ページネーション
テーブルのデータをページネーションするには、テーブルの後に <mat-paginator> を追加します。

テーブルのデータソースに MatTableDataSource を使用している場合は、MatPaginator をデータソースに与えるだけです。これはユーザによるページ変更を自動的に聞き取り、正しいページングされたデータをテーブルに送ります。

そうでなければ、データをページ分割するロジックを実装しているのであれば、パジネータの (ページの) 出力を聞いて、正しいデータのスライスをテーブルに渡す必要があります。

<mat-paginator> の使用法や設定についての詳細は mat-paginator のドキュメントをご覧ください。

MatPaginator はテーブルのデータをページングするための提供されたソリューションのひとつですが、これが唯一の選択肢というわけではありません。実際、MatTable とそのインターフェイスは特定の実装に縛られていないので、テーブルはどんなカスタムページネーション UI や戦略でも動作します。

ソート
テーブルにソートの動作を追加するには,テーブルに matSort 指令を追加し,ソートのトリガーとなる各列ヘッダセルに mat-sort-header を追加します.matSort ディレクティブを初期化するためには,MatSortModule をインポートしなければならないことに注意してください(API ドキュメントを参照してください).


<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let element"> {{element.position}}。</td> </td
</ng-container>
テーブルのデータソースに MatTableDataSource を使用している場合,データソースに MatSort 指令を与えると,自動的にソートの変更を聞き,テーブルによってレンダリングされるデータの順序を変更します.

デフォルトでは、MatTableDataSource は、ソートされた列の名前が、その列が表示するデータプロパティ名と一致するという前提でソートを行います。例えば,次の列定義は,position という名前で,行セルに表示されるプロパティの名前と一致しています.

なお、データプロパティが列名と一致しない場合や、より複雑なデータプロパティアクセサが必要な場合は、カスタム sortDataAccessor 関数を設定して、 MatTableDataSource 上のデフォルトのデータアクセサをオーバーライドすることができます。

MatTableDataSource を使用せずに、データをソートするカスタムロジックを実装する場合は、ソートの (matSortChange) イベントをリッスンし、ソートの状態に応じてデータを並べ替えます。データ配列を直接テーブルに提供している場合は、テーブルの renderRows() を忘れずに呼び出してください。

ソート動作の使用および設定についての詳細は、matSort のドキュメントをご覧ください。

MatSort は,テーブルのデータをソートするための一つのソリューションですが,唯一の選択肢ではありません.実際, MatTable とそのインターフェースは特定の実装に縛られていないので,テーブルは任意のカスタムソート UI や戦略で動作することができます.

フィルタリング
Angular Material は、MatTable のフィルタリングに使用される特定のコンポーネントを提供していません。

一般的な戦略は、ユーザーがフィルタ文字列を入力できる入力を追加し、この入力を聞いてデータソースからテーブルに提供されるデータを変更することです。

MatTableDataSource を使用している場合は、単に MatTableDataSource にフィルタ文字列を提供します。データソースは各行データをシリアル化された形に還元し、フィルタ文字列を含まない場合は行をフィルタリングします。デフォルトでは,行データ削減関数は,すべてのオブジェクト値を連結し,小文字に変換します。

例えば、データオブジェクト {id: 123, name: 'Mr. Smith', favoriteColor: 'blue'}というデータオブジェクトは、123mr. フィルタリングの文字列に blue が含まれていれば、縮小された文字列の中に含まれているため、一致したとみなされ、テーブルに行が表示されます。

デフォルトのフィルタリング動作をオーバーライドするために、カスタムのfilterPredicate関数を設定することができます。この関数は、データオブジェクトとフィルタ文字列を受け取り、データオブジェクトがマッチしたとみなされた場合にtrueを返します。

フィルタにマッチするデータがない場合にメッセージを表示したい場合は、*matNoDataRowディレクティブを使用することができます。

選択
今のところ、テーブルに選択UIを追加する正式なサポートはありませんが、Angular Materialはこれをセットアップするための適切なコンポーネントとピースを提供します。以下の手順は1つのソリューションですが、テーブルに行の選択を組み込む唯一の方法ではありません。

リンク 1. 選択モデルの追加
まず、@angular/cdk/collectionsからSelectionModelを設定し、選択状態を維持することから始めます。

const initialSelection = [];
const allowMultiSelect = true;
this.selection = new SelectionModel<MyDataType>(allowMultiSelect, initialSelection);
2. 選択列の定義
ヘッダーのマスター・トグル・チェックボックスを含む、行のチェックボックスを表示するためのカラム定義を追加します。列名は、ヘッダーとデータ行に提供される表示列のリストに追加されなければなりません。

<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[不確定]="選択.hasValue() && !isAllSelected()">。
</mat-checkbox> </mat-checkbox
</th> <td
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">。
</mat-checkbox> </mat-checkbox
</td> </td
</ng-container>
3. イベント処理ロジックの追加
コンポーネントのロジックに、ヘッダーのマスタートグルを処理し、すべての行が選択されているかどうかをチェックする動作を実装します。

/** 選択された要素の数が総行数と一致するかどうか。*/
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected == numRows;
}

/** すべての行が選択されていない場合はすべての行を選択し、そうでない場合は選択をクリアします。*/
マスタートグル() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.select.select(row));
}
4. オーバーフローのスタイリングを入れる
最後に、selectカラムのスタイリングを調整し、オーバーフローが隠されないようにします。これにより、波紋の効果がセルを超えて広がるようになります。

.mat-column-select {
overflow: initial;
}

フッター行
フッター行定義をテーブルに追加し、フッターセルテンプレートを列定義に追加することで、テーブルにフッター行を追加することができます。フッター行は、レンダリングされたデータ行の後にレンダリングされます。

<ng-container matColumnDef="cost">
<th mat-header-cell *matHeaderCellDef> コスト </th> </th
<td mat-cell *matCellDef="let data"> {{data.cost}}。</td> </td
<td mat-footer-cell *matFooterCellDef> {{totalCost}} です。</td> </td
</ng-container>

...

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>。
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>。
<tr mat-footer-row *matFooterRowDef="columnsToDisplay"></tr> <tr mat-footer-row *matFooterRowDef="columnsToDisplay"></tr>

行と列のスティッキー化
position: stickyスタイリングを使用することで、テーブルの行と列を固定し、スクロールしてもビューポートから出ないようにすることができます。テーブルには、行と列がスティッキーになるように、正しいCSSスタイルを自動的に適用する入力が用意されています。

テーブルを含むスクロールするビューポートの最上部にヘッダー行を固定するためには、matHeaderRowDefにスティッキー入力を追加します。

同様に、これはテーブルのフッター行にも適用できます。ネイティブの<table>を使用しており、Safariを使用している場合、フッターは、レンダリングされたすべてのフッター行にスティッキーが適用された場合にのみ固定されることに注意してください。

また、セルの列を、水平方向にスクロールするビューポートの始点または終点に固定することも可能です。これには、ng-containerのカラム定義にstickyまたはstickyEndディレクティブを追加します。

この機能は、Chrome、Firefox、Safari、およびEdgeでサポートされています。IEではサポートされていませんが、行が単にくっつかないように優雅に失敗します。

なお、Safariモバイルでフレックスベースのテーブルを使用している場合、複数の方向に固まったセルは、スクロールしても正しい位置にとどまることができません。たとえば、ヘッダー行が一番上に固まっていて、1列目も固まっている場合、左上のセルがスクロール時にぎこちなく見えることになります。

また、Edgeのスティッキーポジショニングは、特殊なケースでは揺れて表示されます。例えば、スクロールコンテナに複雑なボックスシャドウがあり、兄弟要素がある場合、スタックしたセルがぎこちなく表示されます。現在、この問題を解決するために、Edgeに未解決の問題があります。

複数行のテンプレート
multiTemplateDataRowsディレクティブを使用して、各データオブジェクトの複数の行をサポートする場合、*matRowDefのコンテキストは、インデックス値がdataIndexとrenderIndexに置き換えられている以外は同じです。

アクセシビリティ
テキストやラベルのないテーブルには、aria-labelまたはaria-labelledbyで意味のあるラベルを付けるべきです。aria-readonlyが設定されていない場合は、デフォルトでtrueが設定されます。

テーブルのデフォルトの役割は grid ですが、role 属性によって treegrid に変更することができます。

mat-table は、フォーカス/キーボードインタラクションを、それ自体では管理しません。ユーザーは、自分のアプリケーションに必要なフォーカス/キーボードインタラクションを追加することができます。

display: flex のテーブル
MatTable は、ネイティブな HTML テーブルを使用する必要はありません。代わりに、テーブルのスタイルに display: flex を使用する代替アプローチを使用することができます。

この代替アプローチは、ネイティブのテーブル要素タグを、MatTable ディレクティブのセレクタで置き換えます。例えば、<table mat-table> は <mat-table> となり、<tr mat-row> は <mat-row> となります。以下は、この代替テンプレートを使用した前例です。

<mat-table [dataSource]="dataSource"> (マットテーブル)

<ng-container matColumnDef="username">
<mat-header-cell *matHeaderCellDef> ユーザー名 </mat-header-cell> </mat-header-cell
<mat-cell *matCellDef="let row"> {{row.username}}。</mat-cell>
</ng-container>

<! -- 年齢の定義 -->
<ng-container matColumnDef="age">
<mat-header-cell *matHeaderCellDef> 年齢 </mat-header-cell> </mat-header-cell
<mat-cell *matCellDef="let row"> {{row.age}}。</mat-cell>
</ng-container>


<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef> タイトル </mat-header-cell> </mat-header-cell
<mat-cell *matCellDef="let row"> {{row.title}}。</mat-cell>
</ng-container>


<mat-header-row *matHeaderRowDef="['username', 'age', 'title']"></mat-header-row>。
<mat-row *matRowDef="let row; columns: ['username', 'age', 'title']"></mat-row> </mat-row
</mat-table>
この方法では、colspan/rowspanのようなネイティブテーブルの機能を含めたり、内容に応じてサイズを変更するカラムを持つことができないことに注意してください。

ログインするとコメントできます