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


はじめに

この記事はAngular Advent Calendar 2018 15日目の記事です。

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

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

日付入力のブラウザ間の違いを見てみる

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

サポートしたいブラウザで最低限のことができれば、Materialを入れないほうが
バンドルサイズも減るだろうし…という感じのテンションで
MDNのドキュメントにあるサンプルで試してみました。

macOS

Safari Chrome Firefox

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

Windows 10

IE11 Chrome Edge

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に被せるような感じにしています。

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 { }
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で日付を選択できるようになって、統一感が増しました
[(ngModel)]ではDate型で入ってくるのも嬉しいポイントでした。いちいちパースとかしなくていいのも嬉しい!

日本語化する

このままでも十分最高な日付入力UIですが、サービスによって
はカレンダーの表示が英語だと厳しいところもあるかと思います。
inputの中身も14/12/2018とかいう日本人に馴染みがない表記になっちゃうし。

そこで、サクッと
ドキュメントを参考に app.module.ts に以下のような記述を追加したところ

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


日本語になりました 👏👏

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

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

いい感じになりました!

日付入力をアツくカスタマイズする

入力可能な日付を制限する

Angular Materialならいつからいつまで、といった範囲を指定して
ユーザーに日付を入力させることが可能です。

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;
}
app.component.html
<input
  class="date-input"
  [min]="minDate"
  [max]="maxDate"
  [(ngModel)]="date"
  [matDatepicker]="picker"
  placeholder="Choose a date">

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

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

app.component.html
 <mat-datepicker [startAt]="startAt" #picker></mat-datepicker>
app.component.ts
startAt = new Date(1996, 2, 10)

カレンダーを開いたときに最初に表示させるUI

誕生日を選ばせるんだし、最初は年を選ばせたいなあ、みたいなケースにもバッチリです。

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

まとめ

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

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