Angularでinputに日付フォーマット
Angular Materialを使ってngModel(双方向データバインディング)を使った入力フォームを作成している過程で、inputに日付フォーマットがほしくなりました。ただ執筆時点のmatInputにバグ?がありtypeをdateとしたときフォーマットが崩れてしまうため、自作することにしました。
ここではAngular Materialに実装しましたが、Angularで動く部分なのでMaterialでなくても利用できると思います。
ただしシングルバイト文字の入力なら問題ないのですが、IMEの効いた状態では思うようになりません。
また、Angularを含むJavaScript環境で入力欄のフォーマットに悩んでいるようなら、ここでは触れませんがCleave.jsという選択肢もあります。
Angularでデータを日付の形式で扱う場合はQuiita:「Angular 6 で、日付や時刻との双方向データバインディング 」で紹介している方法を採用すれば良さそうです。筆者の場合は文字列として処理したかったため、先のサイトを参考に自作しました。
双方向データバインディングのおさらい
双方向データバインディングを設定するにはHTMLの属性に、[(ngModel)]="param"と記述すればよかったです。これは次のように分かち書きができます。
[]でスクリプト側からのデータバインディング、()でHTML側からのデータバインディングを設定しています。
パイプ
双方向データバインディングである[(ngModel)]を先のように分割することで、スクリプト側からの処理でパイプが利用できるようになります。
パイプとは特定のルールに基づいて文字を整形するものでした。たとえば3桁毎に,を付与する「currency」などがあります。
先の参考サイトの通りにtypeがtextのinputにdateパイプを摘要してみます。dateパイプは:の後の文字列でフォーマットを指定できます。
結果は期待した通りになりません。1文字数値を入力した段階で「1970-01-01」がセットされてしまいます。
こうなる理由は一文字目を入力した段階で値を日付(date)として扱って、それをフォーマットしてしまうためです。
つまりdateパイプでは文字列での日付フォーマットができません。
パイプの自作
そこでQuiita:「[Angular]カスタムパイプの作り方」を参考にパイプを自作します。
コマンドプロンプトから、アプリのディレクトリで次のコマンドを実行してパイプのひな形を作成します。
--nameにパイプの名前、--moduleには登録するモジュールを設定します(ここではapp.module.tsを指定しています)。
ファイル名は[--nameで指定した名前].pipe.tsとなります。この中身は初期状態では次のようになっています。
修正前.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'strymd'
})
export class StrymdPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
transformの部分で変換式を作成します。ここでは引数は常に一つなので、それも修正しました。
基本的にはスクリプト側にある変数の値を整形して返すだけですが、初期状態で「//」などの文字にならないように文字長によって表示を変えています。またバックスペースで数値を消した時に/を自動で消す効果もあります。
先にスクリプト側の変数で/を保持する(例:2021/01/03)例を書きます。/を除去して持つ場合の方法も後述します。
修正後.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'strymd'
})
export class StrymdPipe implements PipeTransform {
transform(valueWithSlash: string): string {
//スラッシュを除去
let value: string = valueWithSlash.replace(/¥//g,'');
if (value.length < 7) {
switch(value.length) {
case 0:
case 1:
case 2:
case 3:
case 4:
return value;
case 5:
case 6:
return value.substr(0,4)+"/"+value.substr(4,2);
}
} else {
return value.substr(0,4)+"/"+value.substr(4,2)+"/"+value.substr(6,2);
}
}
}
Angular HTML
HTMLでは次のようにします。コマンドラインからパイプを追加した際に自動でapp.moduleに登録されているので、利用するパイプの名前を変えるだけです。
/をスクリプトデータ内では除去する
入力時は/で区分けした方がわかりやすいですが、DBに格納する際などは/がある分だけ容量を使うので除去したいケースが多いと思います。(それ以前に日付を文字列として扱うべきではないという話もありますが……。)
HTML側からのデータバインディング時に/を取り除く関数removeSlashを付与してそれを実装します。
any.component.ts
...
removeSlash(strYmd: string, strDataName: string):void {
switch(strDataName) {
case 'param':
this.param=strYmd.replace(/\//g,'');
break;
default:
}
}
...
こうすると、パイプ側のスラッシュ除去処理は不要になります。そのままでも動きますが、削除しておきます。
再修正後.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'strymd'
})
export class StrymdPipe implements PipeTransform {
transform(value: string): string {
if (value.length < 7) {
switch(value.length) {
case 0:
case 1:
case 2:
case 3:
case 4:
return value;
case 5:
case 6:
return value.substr(0,4)+"/"+value.substr(4,2);
}
} else {
return value.substr(0,4)+"/"+value.substr(4,2)+"/"+value.substr(6,2);
}
}
}
参考にさせていただいたサイトの皆様ありがとうございました。