Angular の Renderer2#listen を Observable として扱う

Angular でイベントを購読したいときはテンプレートで <button (click)="onClick()">click me!</button> とするか、 コンポーネントのメソッドで @HostListener('click') onClick() {} とすればオッケーです。
ですが、まれに Renderer2#listen を使ってイベント購読したいときがありますね。ね?

その Renderer2#listen を Observable として扱う方法です。

前回、 BaseComponent を作りましたが、それに追記する形で listenAsObservable メソッドを定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { OnDestroy } from '@angular/core'
import { Subject } from 'rxjs/Subject'
export abstract class BaseComponent implements OnDestroy {
private destroySource = new Subject()
protected onDestroy$ = this.destroySource.asObservable()
constructor(
protected renderer: Renderer2,
) {}
ngOnDestroy() {
this.destroySource.next()
this.destroySource.complete()
}
protected listenAsObservable(target: 'window' | 'document' | 'body' | any, eventName: string): Observable<Event> {
let stopListening: Function
return Observable.fromEventPattern(
fn => stopListening = this.renderer.listen(target, eventName, fn),
() => stopListening()
)
}
}

サブクラス側ではこんな感じで使います。
例ではグローバルのクリックイベントを捕捉してコンポーネントの状態を変更する、みたいな処理のコード断片をイメージしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Component, Renderer2, ElementRef } from '@angular/core'
import { BaseComponent } from './base-component'
@Component({ ... })
export class TooltipComponent extends BaseComponent {
opened = false
constructor(
renderer: Renderer2,
elementRef: ElementRef,
) {
super(renderer)
// 要素の外側をクリックされたらツールチップを非表示にする
this.listenAsObservable('document', 'click')
.filter(() => !this.opened)
.takeUntil(this.onDestroy$)
.subscribe(event => {
const { nativeElement } = this.elementRef
const clickedOutside = !nativeElement.contains(event.target)
if (clickedOutside) {
this.opened = false
}
})
}
open() {
this.opened = true
}
}

HostListener でも @HostListener('document:click') と引数を渡すことでコンポーネントの外のイベントを補足できますが、こちらのやり方をつかうメリットとしては、 Observable として扱えるので各種オペレーターが使える、 ngOnDestroy のときにイベント購読解除のメソッドを呼ばなくても、 takeUntil(this.onDestroy$) を追加するだけでいいといった点があります。