-
Notifications
You must be signed in to change notification settings - Fork 6.8k
/
Copy pathmap-event-manager.ts
98 lines (81 loc) · 3 KB
/
map-event-manager.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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* @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.dev/license
*/
import {NgZone} from '@angular/core';
import {BehaviorSubject, Observable, Subscriber} from 'rxjs';
import {switchMap} from 'rxjs/operators';
type MapEventManagerTarget =
| {
addListener<T>(
name: string,
callback: (args: T) => void,
): google.maps.MapsEventListener | undefined;
}
| undefined;
/** Manages event on a Google Maps object, ensuring that events are added only when necessary. */
export class MapEventManager {
/** Pending listeners that were added before the target was set. */
private _pending: {observable: Observable<unknown>; observer: Subscriber<unknown>}[] = [];
private _listeners: google.maps.MapsEventListener[] = [];
private _targetStream = new BehaviorSubject<MapEventManagerTarget>(undefined);
/** Clears all currently-registered event listeners. */
private _clearListeners() {
for (const listener of this._listeners) {
listener.remove();
}
this._listeners = [];
}
constructor(private _ngZone: NgZone) {}
/** Gets an observable that adds an event listener to the map when a consumer subscribes to it. */
getLazyEmitter<T>(name: string): Observable<T> {
return this._targetStream.pipe(
switchMap(target => {
const observable = new Observable<T>(observer => {
// If the target hasn't been initialized yet, cache the observer so it can be added later.
if (!target) {
this._pending.push({observable, observer});
return undefined;
}
const listener = target.addListener(name, (event: T) => {
this._ngZone.run(() => observer.next(event));
});
// If there's an error when initializing the Maps API (e.g. a wrong API key), it will
// return a dummy object that returns `undefined` from `addListener` (see #26514).
if (!listener) {
observer.complete();
return undefined;
}
this._listeners.push(listener);
return () => listener.remove();
});
return observable;
}),
);
}
/** Sets the current target that the manager should bind events to. */
setTarget(target: MapEventManagerTarget) {
const currentTarget = this._targetStream.value;
if (target === currentTarget) {
return;
}
// Clear the listeners from the pre-existing target.
if (currentTarget) {
this._clearListeners();
this._pending = [];
}
this._targetStream.next(target);
// Add the listeners that were bound before the map was initialized.
this._pending.forEach(subscriber => subscriber.observable.subscribe(subscriber.observer));
this._pending = [];
}
/** Destroys the manager and clears the event listeners. */
destroy() {
this._clearListeners();
this._pending = [];
this._targetStream.complete();
}
}