diff --git a/goldens/google-maps/index.api.md b/goldens/google-maps/index.api.md index 6e19c7ae005b..c3e4bc5028a3 100644 --- a/goldens/google-maps/index.api.md +++ b/goldens/google-maps/index.api.md @@ -320,14 +320,14 @@ export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAncho getAnchor(): google.maps.marker.AdvancedMarkerElement; set gmpDraggable(draggable: boolean); readonly mapClick: Observable; - readonly mapDblclick: Observable; + readonly mapDblclick: Observable; readonly mapDrag: Observable; readonly mapDragend: Observable; readonly mapDragstart: Observable; - readonly mapMouseout: Observable; + readonly mapMouseout: Observable; readonly mapMouseover: Observable; - readonly mapMouseup: Observable; - readonly mapRightclick: Observable; + readonly mapMouseup: Observable; + readonly mapRightclick: Observable; readonly markerInitialized: EventEmitter; // (undocumented) ngOnChanges(changes: SimpleChanges): void; @@ -493,7 +493,7 @@ export class MapDirectionsService { export class MapEventManager { constructor(_ngZone: NgZone); destroy(): void; - getLazyEmitter(name: string): Observable; + getLazyEmitter(name: string, type?: 'custom' | 'native'): Observable; setTarget(target: MapEventManagerTarget): void; } diff --git a/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts b/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts index 2f6cf7acd71b..61f0d22bce59 100644 --- a/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts +++ b/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts @@ -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(() => { diff --git a/src/google-maps/map-advanced-marker/map-advanced-marker.ts b/src/google-maps/map-advanced-marker/map-advanced-marker.ts index b21c2401726f..88d5ea6f6228 100644 --- a/src/google-maps/map-advanced-marker/map-advanced-marker.ts +++ b/src/google-maps/map-advanced-marker/map-advanced-marker.ts @@ -135,32 +135,32 @@ export class MapAdvancedMarker /** * This event is fired when the AdvancedMarkerElement is double-clicked. */ - @Output() readonly mapDblclick: Observable = - this._eventManager.getLazyEmitter('dblclick'); + @Output() readonly mapDblclick: Observable = + this._eventManager.getLazyEmitter('dblclick', 'native'); /** * This event is fired when the mouse moves out of the AdvancedMarkerElement. */ - @Output() readonly mapMouseout: Observable = - this._eventManager.getLazyEmitter('mouseout'); + @Output() readonly mapMouseout: Observable = + this._eventManager.getLazyEmitter('mouseout', 'native'); /** * This event is fired when the mouse moves over the AdvancedMarkerElement. */ @Output() readonly mapMouseover: Observable = - this._eventManager.getLazyEmitter('mouseover'); + this._eventManager.getLazyEmitter('mouseover', 'native'); /** * This event is fired when the mouse button is released over the AdvancedMarkerElement. */ - @Output() readonly mapMouseup: Observable = - this._eventManager.getLazyEmitter('mouseup'); + @Output() readonly mapMouseup: Observable = + this._eventManager.getLazyEmitter('mouseup', 'native'); /** * This event is fired when the AdvancedMarkerElement is right-clicked. */ - @Output() readonly mapRightclick: Observable = - this._eventManager.getLazyEmitter('rightclick'); + @Output() readonly mapRightclick: Observable = + this._eventManager.getLazyEmitter('auxclick', 'native'); /** * This event is repeatedly fired while the user drags the AdvancedMarkerElement. diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index 54120e7da97a..ff39d9ba0319 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -10,12 +10,19 @@ import {NgZone} from '@angular/core'; import {BehaviorSubject, Observable, Subscriber} from 'rxjs'; import {switchMap} from 'rxjs/operators'; +interface ListenerHandle { + remove(): void; +} + type MapEventManagerTarget = | { addListener( name: string, callback: (...args: T) => void, ): google.maps.MapsEventListener | undefined; + + addEventListener?(name: string, callback: (...args: T) => void): void; + removeEventListener?(name: string, callback: (...args: T) => void): void; } | undefined; @@ -23,7 +30,7 @@ type MapEventManagerTarget = export class MapEventManager { /** Pending listeners that were added before the target was set. */ private _pending: {observable: Observable; observer: Subscriber}[] = []; - private _listeners: google.maps.MapsEventListener[] = []; + private _listeners: ListenerHandle[] = []; private _targetStream = new BehaviorSubject(undefined); /** Clears all currently-registered event listeners. */ @@ -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(name: string): Observable { + /** + * 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(name: string, type?: 'custom' | 'native'): Observable { return this._targetStream.pipe( switchMap(target => { const observable = new Observable(observer => { @@ -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; diff --git a/src/google-maps/testing/fake-google-map-utils.ts b/src/google-maps/testing/fake-google-map-utils.ts index 89b32072ca6f..2b9d132f0901 100644 --- a/src/google-maps/testing/fake-google-map-utils.ts +++ b/src/google-maps/testing/fake-google-map-utils.ts @@ -153,6 +153,8 @@ export function createAdvancedMarkerSpy( ): jasmine.SpyObj { const advancedMarkerSpy = jasmine.createSpyObj('google.maps.marker.AdvancedMarkerElement', [ 'addListener', + 'addEventListener', + 'removeEventListener', ]); advancedMarkerSpy.addListener.and.returnValue({remove: () => {}}); return advancedMarkerSpy;