/
global-change-and-input-listener.ts
67 lines (57 loc) · 2.47 KB
/
global-change-and-input-listener.ts
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {DOCUMENT} from '@angular/common';
import {Inject, Injectable, NgZone, OnDestroy} from '@angular/core';
import {SpecificEventListener} from '@material/base';
import {fromEvent, Observable, Subject, Subscription} from 'rxjs';
import {finalize, share, takeUntil} from 'rxjs/operators';
/**
* Handles listening for all change and input events that occur on the document.
*
* This service exposes a single method #listen to allow users to subscribe to change and input
* events that occur on the document. Since listening for these events can be expensive, we use
* #fromEvent which will lazily attach a listener when the first subscription is made and remove the
* listener once the last observer unsubscribes.
*/
@Injectable({providedIn: 'root'})
export class GlobalChangeAndInputListener<K extends 'change'|'input'> implements OnDestroy {
/** The injected document if available or fallback to the global document reference. */
private _document: Document;
/** Stores the subjects that emit the events that occur on the global document. */
private _observables = new Map<K, Observable<Event>>();
/** The notifier that triggers the global event observables to stop emitting and complete. */
private _destroyed = new Subject();
constructor(@Inject(DOCUMENT) document: any, private _ngZone: NgZone) {
this._document = document;
}
ngOnDestroy() {
this._destroyed.next();
this._destroyed.complete();
this._observables.clear();
}
/** Returns a subscription to global change or input events. */
listen(type: K, callback: SpecificEventListener<K>): Subscription {
// If this is the first time we are listening to this event, create the observable for it.
if (!this._observables.has(type)) {
this._observables.set(type, this._createGlobalEventObservable(type));
}
return this._ngZone.runOutsideAngular(() =>
this._observables.get(type)!.subscribe((event: Event) =>
this._ngZone.run(() => callback(event))
)
);
}
/** Creates an observable that emits all events of the given type. */
private _createGlobalEventObservable(type: K) {
return fromEvent(this._document, type, {capture: true, passive: true}).pipe(
takeUntil(this._destroyed),
finalize(() => this._observables.delete(type)),
share(),
);
}
}