AngularMaterialのTableで任意の値（松竹梅など）をSortする方法

# はじめに

Angular Material のTableコンポーネントでSortするときに、単純な数値や文字列の順序ではなく、独自のステータス（松竹梅など）でSortする方法をまとめます。
※　[Angular Material](https://material.angular.io/)はGoogleが提唱している[Material Design](https://material.io/)に準拠したAngular用のUIコンポーネント集です。

# 基本となるテーブル

まず、基本的なSortに対応したテーブルを作成します。

* sample-table.component.html

``` html
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
  <!-- no Column -->
  <ng-container matColumnDef="no">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> No. </th>
    <td mat-cell *matCellDef="let element"> {{element.no}} </td>
  </ng-container>

  <!-- Name Column -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <!-- course Column -->
  <ng-container matColumnDef="course">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Course </th>
    <td mat-cell *matCellDef="let element"> {{element.course}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
```

* sample-table.component.ts

``` typescript
export interface Reservation {
  no: number;
  name: string;
  course: string;
}

const SAMPLE_DATA: Reservation[] = [
  {no: 0, name: "佐藤", course: "松"},
  {no: 1, name: "鈴木", course: "松"},
  {no: 2, name: "高橋", course: "梅"},
  {no: 3, name: "田中", course: "松"},
  {no: 4, name: "伊藤", course: "竹"},
  {no: 5, name: "渡辺", course: "松"},
  {no: 6, name: "山本", course: "梅"},
  {no: 7, name: "中村", course: "竹"},
  {no: 8, name: "小林", course: "竹"},
  {no: 9, name: "加藤", course: "梅"},
  {no: 10, name: "吉田", course: "松"},
  {no: 11, name: "山田", course: "竹"},
  {no: 12, name: "佐々木", course: "松"},
];

@Component({
  selector: 'sample-table',
  styleUrls: ['sample-table.component.css'],
  templateUrl: 'sample-table.component.html',
})
export class SampleTableComponent implements OnInit {
  displayedColumns: string[] = ['no', 'name', 'course'];
  dataSource = new MatTableDataSource(SAMPLE_DATA);

  @ViewChild(MatSort, {static: true}) sort: MatSort;

  ngOnInit() {
    this.dataSource.sort = this.sort;
  }
}
```

* sample-table.component.css

``` css
table {
  width: 100%;
}

th.mat-sort-header-sorted {
  color: black;
}
```

ちなみに、この段階で、courseで昇順にすると、松→梅→竹になります。 
これを松→竹→梅になるように対応します。

# Sort順序のカスタマイズ

Sort順序のカスタマイズには`MatTableDataSource`の`sortingDataAccessor`を利用します。 
`sortingDataAccessor`の型定義は下記の通りです。

```
(data: T, sortHeaderId: string) => string | number
```
 
dataはSAMPLE_DATAの１行分のデータが入ります。そのため、この関数は行数分コールされます。
sortHeaderIdはSort対象のidが入ります。（courseカラムのSortを実行した場合は"course"）

Sort順序をカスタマイズするには、下記のように`sortingDataAccessor`へ関数を代入します。

* sample-table.component.ts

``` typescript
ngOnInit() {
  this.dataSource.sort = this.sort;
  this.dataSource.sortingDataAccessor = (item, property) => {
    if (property == "course") {
      switch (item.course) {
        case "松":
          return 0;
        case "竹":
          return 1;
        case "梅":
          return 2;
        default:
          return 3;
      }
    }
    return (item as any)[property];
  };
}
```

まず、`if (property == "course")`でcourseカラムのSortのときだけ動作するようにします。それ以外は`return (item as any)[property];`でデフォルト動作になるようにしています。 
if文の中では松竹梅に合わせてnumber型を返します。これにより、任意の値のSort順序を制御できます。