Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions goldens/google-maps/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,14 @@ export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAncho
getAnchor(): google.maps.marker.AdvancedMarkerElement;
set gmpDraggable(draggable: boolean);
readonly mapClick: Observable<google.maps.MapMouseEvent>;
readonly mapDblclick: Observable<google.maps.MapMouseEvent>;
readonly mapDblclick: Observable<MouseEvent>;
readonly mapDrag: Observable<google.maps.MapMouseEvent>;
readonly mapDragend: Observable<google.maps.MapMouseEvent>;
readonly mapDragstart: Observable<google.maps.MapMouseEvent>;
readonly mapMouseout: Observable<google.maps.MapMouseEvent>;
readonly mapMouseout: Observable<MouseEvent>;
readonly mapMouseover: Observable<google.maps.MapMouseEvent>;
readonly mapMouseup: Observable<google.maps.MapMouseEvent>;
readonly mapRightclick: Observable<google.maps.MapMouseEvent>;
readonly mapMouseup: Observable<MouseEvent>;
readonly mapRightclick: Observable<MouseEvent>;
readonly markerInitialized: EventEmitter<google.maps.marker.AdvancedMarkerElement>;
// (undocumented)
ngOnChanges(changes: SimpleChanges): void;
Expand Down Expand Up @@ -493,7 +493,7 @@ export class MapDirectionsService {
export class MapEventManager {
constructor(_ngZone: NgZone);
destroy(): void;
getLazyEmitter<T>(name: string): Observable<T>;
getLazyEmitter<T>(name: string, type?: 'custom' | 'native'): Observable<T>;
setTarget(target: MapEventManagerTarget): void;
}

Expand Down
21 changes: 11 additions & 10 deletions src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,21 @@ describe('MapAdvancedMarker', () => {
const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS);
createAdvancedMarkerConstructorSpy(advancedMarkerSpy);

const addSpy = advancedMarkerSpy.addListener;
const customSpy = advancedMarkerSpy.addListener;
const nativeSpy = advancedMarkerSpy.addEventListener;
const fixture = TestBed.createComponent(TestApp);
fixture.detectChanges();
flush();

expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function));
expect(addSpy).toHaveBeenCalledWith('dblclick', jasmine.any(Function));
expect(addSpy).toHaveBeenCalledWith('mouseout', jasmine.any(Function));
expect(addSpy).toHaveBeenCalledWith('mouseover', jasmine.any(Function));
expect(addSpy).toHaveBeenCalledWith('mouseup', jasmine.any(Function));
expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function));
expect(addSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function));
expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function));
expect(addSpy).not.toHaveBeenCalledWith('dragstart', jasmine.any(Function));
expect(customSpy).toHaveBeenCalledWith('click', jasmine.any(Function));
expect(nativeSpy).toHaveBeenCalledWith('dblclick', jasmine.any(Function));
expect(nativeSpy).toHaveBeenCalledWith('mouseout', jasmine.any(Function));
expect(nativeSpy).toHaveBeenCalledWith('mouseover', jasmine.any(Function));
expect(nativeSpy).toHaveBeenCalledWith('mouseup', jasmine.any(Function));
expect(nativeSpy).toHaveBeenCalledWith('auxclick', jasmine.any(Function));
expect(customSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function));
expect(customSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function));
expect(customSpy).not.toHaveBeenCalledWith('dragstart', jasmine.any(Function));
}));

it('should be able to add an event listener after init', fakeAsync(() => {
Expand Down
18 changes: 9 additions & 9 deletions src/google-maps/map-advanced-marker/map-advanced-marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,32 +135,32 @@ export class MapAdvancedMarker
/**
* This event is fired when the AdvancedMarkerElement is double-clicked.
*/
@Output() readonly mapDblclick: Observable<google.maps.MapMouseEvent> =
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('dblclick');
@Output() readonly mapDblclick: Observable<MouseEvent> =
this._eventManager.getLazyEmitter<MouseEvent>('dblclick', 'native');

/**
* This event is fired when the mouse moves out of the AdvancedMarkerElement.
*/
@Output() readonly mapMouseout: Observable<google.maps.MapMouseEvent> =
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('mouseout');
@Output() readonly mapMouseout: Observable<MouseEvent> =
this._eventManager.getLazyEmitter<MouseEvent>('mouseout', 'native');

/**
* This event is fired when the mouse moves over the AdvancedMarkerElement.
*/
@Output() readonly mapMouseover: Observable<google.maps.MapMouseEvent> =
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('mouseover');
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('mouseover', 'native');

/**
* This event is fired when the mouse button is released over the AdvancedMarkerElement.
*/
@Output() readonly mapMouseup: Observable<google.maps.MapMouseEvent> =
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('mouseup');
@Output() readonly mapMouseup: Observable<MouseEvent> =
this._eventManager.getLazyEmitter<MouseEvent>('mouseup', 'native');

/**
* This event is fired when the AdvancedMarkerElement is right-clicked.
*/
@Output() readonly mapRightclick: Observable<google.maps.MapMouseEvent> =
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('rightclick');
@Output() readonly mapRightclick: Observable<MouseEvent> =
this._eventManager.getLazyEmitter<MouseEvent>('auxclick', 'native');

/**
* This event is repeatedly fired while the user drags the AdvancedMarkerElement.
Expand Down
44 changes: 36 additions & 8 deletions src/google-maps/map-event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ import {NgZone} from '@angular/core';
import {BehaviorSubject, Observable, Subscriber} from 'rxjs';
import {switchMap} from 'rxjs/operators';

interface ListenerHandle {
remove(): void;
}

type MapEventManagerTarget =
| {
addListener<T extends unknown[]>(
name: string,
callback: (...args: T) => void,
): google.maps.MapsEventListener | undefined;

addEventListener?<T extends unknown[]>(name: string, callback: (...args: T) => void): void;
removeEventListener?<T extends unknown[]>(name: string, callback: (...args: T) => void): void;
}
| 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 _listeners: ListenerHandle[] = [];
private _targetStream = new BehaviorSubject<MapEventManagerTarget>(undefined);

/** Clears all currently-registered event listeners. */
Expand All @@ -37,8 +44,12 @@ export class MapEventManager {

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> {
/**
* Gets an observable that adds an event listener to the map when a consumer subscribes to it.
* @param name Name of the event for which the observable is being set up.
* @param type Type of the event (e.g. one going to a DOM node or a custom Maps one).
*/
getLazyEmitter<T>(name: string, type?: 'custom' | 'native'): Observable<T> {
return this._targetStream.pipe(
switchMap(target => {
const observable = new Observable<T>(observer => {
Expand All @@ -48,19 +59,36 @@ export class MapEventManager {
return undefined;
}

const listener = target.addListener(name, (event: T) => {
let handle: ListenerHandle;
const listener = (event: T) => {
this._ngZone.run(() => observer.next(event));
});
};

if (type === 'native') {
if (
(typeof ngDevMode === 'undefined' || ngDevMode) &&
(!target.addEventListener || !target.removeEventListener)
) {
throw new Error(
'Maps event target that uses native events must have `addEventListener` and `removeEventListener` methods.',
);
}

target.addEventListener!(name, listener);
handle = {remove: () => target.removeEventListener!(name, listener)};
} else {
handle = target.addListener(name, listener)!;
}

// 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) {
if (!handle) {
observer.complete();
return undefined;
}

this._listeners.push(listener);
return () => listener.remove();
this._listeners.push(handle);
return () => handle.remove();
});

return observable;
Expand Down
2 changes: 2 additions & 0 deletions src/google-maps/testing/fake-google-map-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ export function createAdvancedMarkerSpy(
): jasmine.SpyObj<google.maps.marker.AdvancedMarkerElement> {
const advancedMarkerSpy = jasmine.createSpyObj('google.maps.marker.AdvancedMarkerElement', [
'addListener',
'addEventListener',
'removeEventListener',
]);
advancedMarkerSpy.addListener.and.returnValue({remove: () => {}});
return advancedMarkerSpy;
Expand Down
Loading