DatePickerに対して多言語対応を意識しつつ和暦入力できるようにする@Angular

# 1. はじめに
Angularには日付選択のComponentとしてDatePickerが提供されています。非常に優れた実装で、何も考えずにそのまま使っても良いのですが、和暦入力に対応させていきたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/15d1a451-2d45-b83c-1011-3ae3f1a77588.png)

# 2. 日付処理を行うライブラリを切り替える
和暦の実装を行うにあたって、いつからいつまでが平成なのかと言った情報を管理する必要があります。これらの情報をIntlというオブジェクトで管理してくれるようなのですが、新元号対応にあたって各ブラウザのデータ更新の足並みが揃うわけでもなく、利用するブラウザや端末によって画面表記がマチマチになってしまうという弊害があります。合わせて、日付の扱いは基本的に手間がかかり、Timezoneなどが入ってくると難易度がかなり上がってきます。そこで、専用のライブラリ導入という話が出てくるのですが、AngularのDatePickerは最初からそれらが考慮されています。標準の実装で、以下3つから選択することができます。

1. Intl(ブラウザ標準)
1. date-fns
1. Luxon
1. Moment.js

今回、この中から最も知名度が高い（であろう）Moment.jsを選択します。なお、moment.jsは今後開発を行わないことをHP上で宣言しているので、その他ライブラリを使ったほうが良いと思われます。

切り替え方法は非常に簡単で、AppModuleの@NgModule.importsに``MatMomentDateModule``を追加するだけです。

```typescript
    :
    :
@NgModule({
    :
    :
  imports: [
    :
    :
    MatNativeDateModule, //MatNativeDateModuleもそのまま残しておくのが正解？
    MatMomentDateModule, //moment.jsへの切り替えのために追加
    :
    :
  ],
    :
    :
})
export class AppModule {}
```

この状態で画面表示を行うと、以下のように少しラベルが変化します。

1. 年月の表示が``2月 2034``のようになる
1. 日付に``日``の表示されなくなる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/b5710fb1-5a5b-07e7-8d2b-107d7fbc7241.png)


# 3. 和暦対応を行う
和暦対応を行うためにDateAdapterを実装します。ただし、一から実装するのではなく、Moment.js用に用意されているMomentDateAdapterを継承し、和暦表示用にカスタマイズしていきます。

カレンダーの表記を和暦とするのに合わせて、**入力フィールドの表示についても和暦にしてしまいます**。（普通に実装すると非常に手間がかかりますが、日付書式を切り替えるだけで非常に簡単に実装できます。）

なお、ロケール設定が日本以外の場合、Moment.js標準の表記（西暦）となるようにします。

DateAdapterの実装物は以下の通りです。

```typescript
class MomnetDateAdapter2 extends MomentDateAdapter {
  yearLabel = 'NNNNyo';
  monthYearLabelJa = 'NNNNyoMMM';
  monthYearA11yLabel = 'NNNNyoMMM';
  dateInput = 'NNNNyoMMMDo';

  //ロケールが日本かどうか判定
  private isJa(): boolean {
    return new String(this.locale).toLocaleLowerCase().startsWith('ja');
  }

  //画面に表示する各種日付の書式変換に当たって、
  //年月を表示する場合は月年にならないようにフォーマットを差し替える
  override format(date: Moment, displayFormat: string): string {
    if (this.isJa()) {
      //日本語を指定している場合は和暦入力に
      //画面表示用
      if (displayFormat === MAT_MOMENT_DATE_FORMATS.display.monthYearLabel) {
        return super.format(date, this.monthYearLabelJa);
      }
      //A11y用
      if (
        displayFormat === MAT_MOMENT_DATE_FORMATS.display.monthYearA11yLabel
      ) {
        return super.format(date, this.monthYearA11yLabel);
      }
      //入力欄への表示用
      if (displayFormat === MAT_MOMENT_DATE_FORMATS.display.dateInput) {
        return super.format(date, this.dateInput);
      }
      //年月表記以外は親クラスの処理をそのまま利用
      return super.format(date, displayFormat);
    }
    return super.format(date, displayFormat);
  }

  //入力欄に入力された和暦文字列を日付として解釈するための処理
  //ロケールが日本の場合は書式を入れ替えて処理する
  override parse(value: any, parseFormat: string | string[]): Moment | null {
    if (this.isJa()) {
      if (typeof parseFormat == 'string') {
        return super.parse(value, this.dateInput);
      }
    }
    return super.parse(value, parseFormat);
  }

  //ロケールが日本の場合は、和暦を表示するように書式を切り替える
  override getYearName(date: Moment): string {
    if (this.isJa()) {
      const date2 = this.clone(date);
      return date2.format(this.yearLabel);
    }
    return super.getYearName(date);
  }

  //ロケールが日本の場合は、1日、2日・・・となるように変換
  override getDateNames(): string[] {
    let ret = super.getDateNames();
    if (this.isJa()) {
      ret = ret.map((e) => e + '日');
    }
    return ret;
  }
}
```

実装したDateAdapterをDatePickerに反映するためにAppModuleにて読み込みます。

```typescript
    :
    :
@NgModule({
    :
    :
  providers: [
    { provide: MAT_DATE_LOCALE, useValue: 'ja-JP' }, //初期のロケールを日本に
    { provide: DateAdapter, useClass: MomnetDateAdapter2 }, //作成したDateAdapterをDatePickerに反映
  ],
    :
    :
})
export class AppModule {}
```

適用すると以下の用に表示が切り替わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/4f5e40a5-8a01-958f-cdae-49c9eb509ca2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/4f44a28f-e00f-2d16-98d0-d80c620dd435.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/1be2ad75-0ff7-1caa-ff24-ed9380f2f416.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490256/52982291-0515-f503-6c98-037044874d31.png)


# 4. 実装サンプル

実装サンプルは以下を参照してください。

https://stackblitz.com/edit/angular-wareki-datepicker-ver01-for-public?file=src/app/app.module.ts


# 5. 参考
- [Angular MaterialのDatePickerで和暦入力や月日入力に対応する@Qiita](https://qiita.com/hareruya-maro/items/d138849fadf6fb561c61)
- [Angular Material datepicker 導入@Qiita](https://qiita.com/red_picmin/items/c1ce3b5ffeaa2dc0dacc)
- [date inputの救世主!? Angular Materialのdatepickerを使ってみた@Qiita](https://qiita.com/pittanko_pta/items/4766a1dfb9a682b76181)
- [Moment.js Documentation](https://momentjs.com/docs/)
- [MomentDateAdapterのソース@github](https://github.com/angular/components/blob/2f9a59a24c0464cbed7f54cbeb5cba73e6007715/src/material-moment-adapter/adapter/moment-date-adapter.ts)
- [Datepicker@Angular](https://material.angular.io/components/datepicker/overview#internationalization)