LoginSignup
6
4

More than 5 years have passed since last update.

[Angular](Tips) RxJSでキーボードイベントを使いこなす

Last updated at Posted at 2019-01-22

AngularでRxを書いた備忘録

バーコードリーダーで読み込んだ値を、特定のinput要素へ入力する必要があったのでそのときのメモ。(少しマニアックな要件かもしれない)

キーボードイベントをひろう

Angularでキーイベントをひろう場合、特定のDOM要素に対してのイベントをひろう用途なので、以下のように書くことが多い。

hoge.component.html
<input (keyup)="onKeyup($event)">

hoge.component.ts
onKeyup(e: KeyboardEvent){
// ここでeventに対しての処理を書く
}

ここで流れてくるお決まりのKeyboardEvent の中身はここを読むとよさそう。
https://developer.mozilla.org/en-US/docs/Web/Events/keyup

ただ今回は以下を実現する必要があった

  • ブラウザが開いていてブラウザ内のどこかにフォーカスが当たっているときのキーイベントが拾いたい
    • documentのイベントをlisten
  • バーコードリーダーで読み込まれた(入力された)文字の先頭に何文字かついてプレフィックスをもとに通常のキーボードからの入力と判別して、バーコードリーダーからの場合にのみ特定のinputに値が入るようにしたい
    • 1文字ずつ流れてくるキーイベントをある程度蓄積して判定

要素関係なくキーイベントをひろう (documentのイベントをListen)

hoge.component.ts
fromEvent(document, 'keyup')
.subscribe((e: KeyboardEvent)=>{
// ここでeventに対しての処理を書く
});

1文字ずつ流れてくるキーをある程度の時間蓄積して判定

Rxで値を溜めるといえば scan オペレータ。

hoge.component.ts
fromEvent(document, 'keyup').pipe(
  // イベントからkeyStringへ変換
  map((e: KeyboardEvent) => e.key),
  // 前の値とくっつける
  scan((acc, v) => (acc ? acc :'') + v , ''),
 // 100ms間、値が流れてこなかったら次に流す
  debounceTime(100),
).subscribe((e: KeyboardEvent)=>{
// ここでeventに対しての処理を書く
});

ただこう書くと、入力の100ms後にくっついた文字列が流れてくるが、scanは値を貯め続けてしまうのでリセットする必要がある。

scanのリセット用のSubjectを用意しておき、キーイベントのストリームが流れた100msにリセット用のSubjectに対してリセット依頼をかける作戦で書いてみた。
((冗長になってしまったし、もっといい書き方やオペレータあるはず、と思う。。 ))

hoge.component.ts
// keyup用のSubscription
private keyup$: Subscription = new Subscription();
// keyReset用のSubject
private keyReset$: Subject<string> = new Subject<string>();
// バーコードリーダーのprefix
readonly barcodePrefix:string = 'barcode_'

form: FormGroup;  // コンストラクタでformGroupに FormControl('hoge')を作っておくとする

ngOnInit() {
 // documentからキーイベントをひろうObservable
 const documentKeyUp = fromEvent(document, 'keyup')
      .pipe(
        // イベントからkeyへ変換
        map((e: KeyboardEvent) => e.key),
        // 特殊キーを除く
        filter(keyString => keyString && (keyString.length === 1)),
      );

this.keyup$.add(
  // キー入力イベントとリセット用のObservableをマージする
  merge(documentKeyUp$, this.keyReset$)
    .pipe(
      // キーを溜める。'reset'が流れてきたら値をクリアする
      scan((acc, keyString) => {
        if (keyString === 'reset') {
          return null;
        }
        return (acc ? acc : '') + keyString;
      }, ''),
      // タイマーリセットのストリームを止める
      filter(value => value && (value !== '')),
      // 100ms値をためる
      debounceTime(100),
      // ためているキーのリセット依頼
      tap(() => this.keyReset$.next('reset')),
      //  バーコード端末からの入力でない場合は値を流さない(prefix有無の判定)
      filter(v => v && (v.indexOf(this.barcodePrefix) === 0)),
      // prefixを取り除く
      map(v => v.replace(this.barcodePrefix, '')),
  ).subscribe((withoutPrefixValue) => {
    // formへ値をセット
    this.form.get('hoge').setValue(withoutPrefixValue);
  }),
);
}

ngOnDestroy(): void {
  // 後始末
  this.keyup$.unsubscribe();
  this.keyReset$.unsubscribe();
}

さいごに

Documentのイベントのリッスンはパフォーマンス的によくないので、Angularの外側で処理する以下の

this.zone.runOutsideAngular(() => {}) の中でイベントをひろうような書き方をしたかったが、

いまいちわからず、今回はできなかった。。

ChangeDetectionが無駄に走らない書き方や、もっとスッキリ書けるようないいオペレータなどあればどなたか教えてください🙏🏻

6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4