notebook

都内でWEB系エンジニアやってます。

angularでフォーカスが外れたらフォーマットに沿った表示にする

フォームの金額入力とかで、入力時は1000000でフォーカスが外れたら1,000,000といったようにすることで大きい値でも判断しやすくなり入力ミスを減らしたい…..

今回は上記のような要望がちょろっと上がったので実際に試してみました

フォーカスが外れたら3桁区切りでカンマが表示されるようにしてみます

動作としてはこんな感じ

f:id:swfz:20170829021351g:plain

方針

focus,blurのイベント発生時にinputのvalueの値を変える感じになりそうですね

普通にcomponentで実装しても良さそうですが勉強がてらdirectiveで実装してみます

ということで、attribute directiveを使ってfocus,blur時に処理を追加してあげるようにしてみます

やることは下記

  • pipeの実装

数値フォーマットの変更自体がangularのDecimalPipeの機能で事足りるが、カンマ区切りから数値に戻す方の処理がないためその処理だけ追加したpipeを実装する

  • directiveの実装

focus,blurイベントで発火するInput用のdirectiveを実装する

pipeの実装

ng g pipe pipes/number-input

angular-cliのジェネレータで作られたpipeにはtransformメソッドのみなのでblur時に値を戻すメソッドを独自に実装します

  • src/app/pipes/number-input.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Pipe({
  name: 'numberInput'
})
export class NumberInputPipe implements PipeTransform {


  transform(value: any, digits?: string): string {
    return new DecimalPipe('ja').transform(value,digits);
  }

  parse(value: string): string {
    return value.replace(/,/g,'');
  }
}

pipeはテンプレート側からの呼び出しのイメージが強いがts側からでも呼び出すことができる

DecimalPipeをnewする際になぜかlocaleを引数として渡さないといけないようだった

実装の中身まで追ってないけどなんでなんだろう。。。。。

directiveの実装

focusイベントとblurイベントのイベントリスナを設定してそれぞれ対応する処理を記述します

ng g directive directives/number-input
  • src/app/directives/number-input.directive.ts
import {OnInit, Directive, ElementRef, HostListener} from '@angular/core';
import {NumberInputPipe} from "../pipes/number-input.pipe";

@Directive({
  selector: '[numberInput]'
})
export class NumberInputDirective implements OnInit {

  private element: HTMLInputElement;
  private digits: string = '1.0-0';

  constructor(
    private elementRef: ElementRef,
    private numberInputPipe: NumberInputPipe,
  ) {
    this.element = this.elementRef.nativeElement;
  }

  ngOnInit(){
    this.element.value = this.numberInputPipe.transform(this.element.value,this.digits);
  }

  @HostListener("focus", ["$event.target.value"])
  onFocus(value){
    this.element.value = this.numberInputPipe.parse(value);
  }
  @HostListener("blur", ["$event.target.value"])
  onBlur(value){
    this.element.value = this.numberInputPipe.transform(value,this.digits);
  }
}

elementRef.nativeElementで自身を参照できるようにする

this.elementは HTMLInputElementなのでthis.element.valueで入力されたテキストの値を参照できるようになる

あとはイベント発火時に最初に実装したpipe経由でフォーマットの変換を行う処理を実装する

NgModuleへの追加

これはangular-cli経由で追加したらよしなにしてくれるので特に気にすることはないと思っていたが、pipeに関しては自分でprovidersに定義し直す必要があった

  • src/app.module.ts
@NgModule({
  declarations: [
    .....
    .....
    NumberInputDirective
  ],
  .....
  .....
  providers: [
    NumberInputPipe
  ]
})
  • 呼び出し側(html)
<input type="text"
       numberInput
       value="10000">

これだけで終わり!

簡単ですね

まとめ

サンプルではvalidationしていないので実際に使うにはもう一手間必要ですが簡単に実装することができました

今回のようなpipeとdirectiveの組み合わせで他にも細かな共通処理を切り出すことが可能になりそうですね

感想としては、今回directiveに関して色々調べたことでこのライブラリはこれ使ってるのね!みたなのが結構あって勉強になりました。