AngularでRxを書いた備忘録
バーコードリーダーで読み込んだ値を、特定のinput要素へ入力する必要があったのでそのときのメモ。(少しマニアックな要件かもしれない)
キーボードイベントをひろう
Angularでキーイベントをひろう場合、特定のDOM要素に対してのイベントをひろう用途なので、以下のように書くことが多い。
<input (keyup)="onKeyup($event)">
onKeyup(e: KeyboardEvent){
// ここでeventに対しての処理を書く
}
ここで流れてくるお決まりのKeyboardEvent
の中身はここを読むとよさそう。
https://developer.mozilla.org/en-US/docs/Web/Events/keyup
ただ今回は以下を実現する必要があった
- ブラウザが開いていてブラウザ内のどこかにフォーカスが当たっているときのキーイベントが拾いたい
- documentのイベントをlisten
- バーコードリーダーで読み込まれた(入力された)文字の先頭に何文字かついてプレフィックスをもとに通常のキーボードからの入力と判別して、バーコードリーダーからの場合にのみ特定のinputに値が入るようにしたい
- 1文字ずつ流れてくるキーイベントをある程度蓄積して判定
要素関係なくキーイベントをひろう (documentのイベントをListen)
fromEvent(document, 'keyup')
.subscribe((e: KeyboardEvent)=>{
// ここでeventに対しての処理を書く
});
1文字ずつ流れてくるキーをある程度の時間蓄積して判定
Rxで値を溜めるといえば scan
オペレータ。
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に対してリセット依頼をかける作戦で書いてみた。
((冗長になってしまったし、もっといい書き方やオペレータあるはず、と思う。。 ))
// 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が無駄に走らない書き方や、もっとスッキリ書けるようないいオペレータなどあればどなたか教えてください🙏🏻