date inputの救世主!? Angular Materialのdatepickerを使ってみた

# はじめに
この記事は[Angular Advent Calendar 2018](https://qiita.com/advent-calendar/2018/angular) 15日目の記事です。

Webアプリケーションでよく使う入力系のUIに「日付の入力」があると思いますが
これがなかなかサポートするブラウザによっては使い物にならない場合が多く
それをカバーするために様々な日付入力用のライブラリが出ていると思います。

パッと思いつきレベルではjQuery系とかが豊富なイメージですが、
お仕事で作っているプロジェクトではAngular + Angular JSのハイブリッド環境で
「ここにさらにjQueryを入れる」という選択も微妙だったので
Angular Materialのdatepickerを調査として使ってみた記録になります。

# 日付入力のブラウザ間の違いを見てみる
Angular Materialを触り始める前に、ブラウザごとの `<input type="date">`の
見た目や使い勝手の違いを整理していきたいと思います。

サポートしたいブラウザで最低限のことができれば、Materialを入れないほうが
バンドルサイズも減るだろうし…という感じのテンションで
[MDNのドキュメント](https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/date)にあるサンプルで試してみました。

## macOS
| Safari | Chrome | Firefox |
| :---: | :---: | :---: |
|  <img width="189" alt="mac safari.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/08766051-74ed-4990-f082-cf2fa46a067e.png"> | <img width="290" alt="mac chrome 71.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/102541e9-9307-691a-b446-bf1ba204552d.png"> | <img width="280" alt="mac firefox.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/58e8f5e2-8731-fe9b-cccf-942be4a8a91d.png"> |

macOSですが、標準で入っているSafariがカレンダーから選択するUIを提供しておらず、ちょっとガッカリです。
ちなみに、iOSのSafariはドラムロールのUIを提供しているので大丈夫です。
Chrome、Firefoxはカレンダーから選択するUIを提供しているので、良さそうですね。個人的にはFirefoxのUIが
スッキリしてて好印象です。

## Windows 10
| IE11 | Chrome | Edge |
| :---: | :---: | :---: |
| <img width="183" alt="windows ie11.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/326f3219-d187-5db5-334a-8054c6d00da5.png"> | <img width="289" alt="windows chrome 70.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/5f04d85d-a1c9-4173-79eb-835d67d02968.png"> | <img width="324" alt="windows edge.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/3d9d87db-ed70-7561-cfc7-6214196c5348.png"> |

IE11がSafari同様にカレンダーのUIを提供しておらず、残念感が出ています。
ChromeはmacOSと同じくカレンダーのUIを、EdgeもiOS SafariのようなUIが提供されます。
Firefoxのスクショを撮り忘れちゃいましたが、macOSで出ているので大丈夫そうな気がします。

## IE11とSafariをサポートするWebサイトは対応が必須
macOSのSafari、WindowsのIE11では`<input type="date">`で
カレンダー型のUIを出してくれないので何かしらの対応が必要になります。

何かの開始日を指定するような画面でユーザーに「`yyyy-MM-dd`形式で入力してね」は
さすがに不親切すぎるのでカレンダー型のUIを提供したほうが良さそうです。
というわけで次のセクションからAngular Materialのdatepickerを触っていきます。

# 開発環境

お仕事で使っているのはAngular + Angular JSのハイブリッド環境（絶賛移行中）ですが
今回はサクッとやりたいことができるか検証したかったので、
新しいCLIプロジェクトを作成してみます。
(StackBlitzだといろんなブラウザで確認できなかった、気のせいかな…？）

Angular CLI: 7.1.1
Node: 8.11.3
OS: macOS 10.14.2

# AngularCLIプロジェクトを作成
まずは空のAngularプロジェクトをCLIで生成

`ng new angular-material-test --routing --style=styl`

試すだけだからルーティングとかいらないですが、いつものクセです。

# Angular Materialの導入

CLIプロジェクトなので `ng add @angular/material` と入れるだけでほぼ終わります。
途中でマテリアルデザインのカラーパレットを選ばされるので、デフォルトから選ぶもよし、オリジナルを設定するもよしです。
昔Materialを導入しようとしたときはもっとステップあったのに、めちゃくちゃ簡単になってますね。`ng add`最高。

なんとなく、カラーテーマをオリジナルで設定したかったので、そこだけいい感じに。
http://takasdev.hatenablog.com/entry/2017/10/08/125524 を参考にしながら
http://mcg.mbitson.com/#!?mcgpalette0=%23489bc6&themename=mcgtheme で色を作って組み込みました。

# Datepickerを実装する

Inputそのものにマテリアルデザインを適用したくなかったので
ボタンでカレンダーを開く、みたいな実装を行いました。CSSで`button`は透明にして
`.date-input`に被せるような感じにしています。


```typescript:app.module.ts
// ...
import { FormsModule } from '@angular/forms';
import { MatDatepickerModule, MatNativeDateModule } from '@angular/material';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    FormsModule. // 追加
    MatDatepickerModule, // 追加
    MatNativeDateModule  // 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

```html:app.component.html
<div class="date-input-container">
  <input class="date-input" [matDatepicker]="picker" [(ngModel)]="date" placeholder="Choose a date">
  <mat-datepicker #picker></mat-datepicker>
  <div class="button" (click)="picker.open()"></div>
</div>
```

これだけで、Angular Materialのdatepickerを使えるようになりました。
上で出したどのブラウザでも同じUIで日付を選択できるようになって、統一感が増しました :clap: :clap: 
`[(ngModel)]`では`Date`型で入ってくるのも嬉しいポイントでした。いちいちパースとかしなくていいのも嬉しい！

# 日本語化する

このままでも十分最高な日付入力UIですが、サービスによって
はカレンダーの表示が英語だと厳しいところもあるかと思います。
inputの中身も`14/12/2018`とかいう日本人に馴染みがない表記になっちゃうし。
<img width="324" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/97cbd22a-a8c9-3cb2-27a3-c7c88f317884.png">

そこで、サクッと
[ドキュメント](https://material.angular.io/components/datepicker/overview#internationalization)を参考に `app.module.ts` に以下のような記述を追加したところ

```typescript:app.module.ts
providers: [
  {provide: MAT_DATE_LOCALE, useValue: 'ja-JP'}
]
```

<img width="298" alt="スクリーンショット 2018-12-13 23.56.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/e9cf1ae9-0a02-e446-19ca-6826fee62820.png">
日本語になりました 👏👏

あとは「◯日」っていう表示がちょっとゴチャついて見えてしまうので
英語のときみたいに「1, 2, 3, 4」という表記に戻してあげたいと思います。

```typescript:jp-date-adapter.ts
// NativeDateAdapterの一部を上書きしたJPDateAdapterを作る
export class JPDateAdapter extends NativeDateAdapter {
  getDateNames(): string[] {
    return Array.from(Array(31), (v, k) => `${k + 1}`);
  }
}
```

```typescript:app.module.ts
providers: [
   {provide: DateAdapter, useClass: JPDateAdapter}
]
```

<img width="298" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/30152/afa0ea4a-5390-c9b7-f7f9-3da9eaa2e96c.png">

いい感じになりました！

# 日付入力をアツくカスタマイズする
## 入力可能な日付を制限する
Angular Materialならいつからいつまで、といった範囲を指定して
ユーザーに日付を入力させることが可能です。

```typescript:app.component.ts
minDate: Date;
maxDate: Date;

ngOnInit () {
  // 今日の日付から…
  this.minDate = new Date();

  // 一週間後の日付を指定したい
  const date = new Date();
  date.setDate(this.minDate.getDate() + 7);
  this.maxDate = date;
}
```

```html:app.component.html
<input
  class="date-input"
  [min]="minDate"
  [max]="maxDate"
  [(ngModel)]="date"
  [matDatepicker]="picker"
  placeholder="Choose a date">
```

## カレンダーを開いたときに最初に出す日付

カレンダーを最初に表示したときは今日の日付じゃなくて、特定の日がいい〜
みたいなケースもパパっとできちゃいます。

```html:app.component.html
 <mat-datepicker [startAt]="startAt" #picker></mat-datepicker>
```

```typescript:app.component.ts
startAt = new Date(1996, 2, 10)
```

## カレンダーを開いたときに最初に表示させるUI
誕生日を選ばせるんだし、最初は年を選ばせたいなあ、みたいなケースにもバッチリです。

```html:app.component.html
<!-- 'month'(月表示) | 'year'(年表示) | 'multi-year'(複数年表示) -->
 <mat-datepicker [startView]="'multi-year'" #picker></mat-datepicker>
```

# まとめ
- 日付入力の闇をまるっと吸収してくれるAngular Material最高かよ :beers: 
- Angular Material、サンプルが充実してて最高だった :hugging: 
- APIドキュメントを見ているだけでも面白い 🕶️

明日のAngular Advent Calendar 2018 担当は @shira_ さんです！