From 28d5a467c013f6c6ca3a253f4830693d32d47028 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 26 Oct 2020 17:50:39 +0100 Subject: [PATCH] fix(google-maps): allow ground overlay bounds to be changed Allows for the bounds of the Google Maps ground overlay to be changed after initialization. This case is different from other similar components, because Google Maps doesn't provide an API to change the bounds so we have to recreate the overlay any time they change. Fixes #20865. --- .../map-ground-overlay.spec.ts | 23 +++++--- .../map-ground-overlay/map-ground-overlay.ts | 56 +++++++++++-------- .../google-maps/google-maps.d.ts | 3 +- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts b/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts index 225288e3a447..06d9bf9cc67c 100644 --- a/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts +++ b/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts @@ -55,13 +55,6 @@ describe('MapGroundOverlay', () => { expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); }); - it('has an error if required bounds are not provided', () => { - expect(() => { - const fixture = TestBed.createComponent(TestApp); - fixture.detectChanges(); - }).toThrow(new Error('Image bounds are required')); - }); - it('exposes methods that provide information about the Ground Overlay', () => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); createGroundOverlayConstructorSpy(groundOverlaySpy).and.callThrough(); @@ -143,6 +136,22 @@ describe('MapGroundOverlay', () => { expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); }); + it('should recreate the ground overlay when the bounds change', () => { + const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); + createGroundOverlayConstructorSpy(groundOverlaySpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + + const oldOverlay = fixture.componentInstance.groundOverlay.groundOverlay; + fixture.componentInstance.bounds = {...bounds}; + fixture.detectChanges(); + + const newOverlay = fixture.componentInstance.groundOverlay.groundOverlay; + expect(newOverlay).toBeTruthy(); + expect(newOverlay).not.toBe(oldOverlay); + }); + }); @Component({ diff --git a/src/google-maps/map-ground-overlay/map-ground-overlay.ts b/src/google-maps/map-ground-overlay/map-ground-overlay.ts index 9214fba4d021..1be363977723 100644 --- a/src/google-maps/map-ground-overlay/map-ground-overlay.ts +++ b/src/google-maps/map-ground-overlay/map-ground-overlay.ts @@ -11,7 +11,7 @@ import {Directive, Input, NgZone, OnDestroy, OnInit, Output} from '@angular/core'; import {BehaviorSubject, Observable, Subject} from 'rxjs'; -import {map, take, takeUntil} from 'rxjs/operators'; +import {takeUntil} from 'rxjs/operators'; import {GoogleMap} from '../google-map/google-map'; import {MapEventManager} from '../map-event-manager'; @@ -30,6 +30,9 @@ export class MapGroundOverlay implements OnInit, OnDestroy { private readonly _opacity = new BehaviorSubject(1); private readonly _url = new BehaviorSubject(''); + private readonly _bounds = + new BehaviorSubject( + undefined); private readonly _destroyed = new Subject(); /** @@ -46,7 +49,13 @@ export class MapGroundOverlay implements OnInit, OnDestroy { } /** Bounds for the overlay. */ - @Input() bounds: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral; + @Input() + get bounds(): google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral { + return this._bounds.value!; + } + set bounds(bounds: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral) { + this._bounds.next(bounds); + } /** Whether the overlay is clickable */ @Input() clickable: boolean = false; @@ -77,21 +86,30 @@ export class MapGroundOverlay implements OnInit, OnDestroy { constructor(private readonly _map: GoogleMap, private readonly _ngZone: NgZone) {} ngOnInit() { - if (!this.bounds && (typeof ngDevMode === 'undefined' || ngDevMode)) { - throw Error('Image bounds are required'); - } if (this._map._isBrowser) { - this._combineOptions().pipe(take(1)).subscribe(options => { + // The ground overlay setup is slightly different from the other Google Maps objects in that + // we have to recreate the `GroundOverlay` object whenever the bounds change, because + // Google Maps doesn't provide an API to update the bounds of an existing overlay. + this._bounds.pipe(takeUntil(this._destroyed)).subscribe(bounds => { + if (this.groundOverlay) { + this.groundOverlay.setMap(null); + this.groundOverlay = undefined; + } + // Create the object outside the zone so its events don't trigger change detection. // We'll bring it back in inside the `MapEventManager` only for the events that the // user has subscribed to. - this._ngZone.runOutsideAngular(() => { - this.groundOverlay = - new google.maps.GroundOverlay(this._url.getValue(), this.bounds, options); - }); - this._assertInitialized(); - this.groundOverlay.setMap(this._map.googleMap!); - this._eventManager.setTarget(this.groundOverlay); + if (bounds) { + this._ngZone.runOutsideAngular(() => { + this.groundOverlay = new google.maps.GroundOverlay(this._url.getValue(), bounds, { + clickable: this.clickable, + opacity: this._opacity.value, + }); + }); + this._assertInitialized(); + this.groundOverlay.setMap(this._map.googleMap!); + this._eventManager.setTarget(this.groundOverlay); + } }); this._watchForOpacityChanges(); @@ -138,19 +156,9 @@ export class MapGroundOverlay implements OnInit, OnDestroy { return this.groundOverlay.getUrl(); } - private _combineOptions(): Observable { - return this._opacity.pipe(map(opacity => { - const combinedOptions: google.maps.GroundOverlayOptions = { - clickable: this.clickable, - opacity, - }; - return combinedOptions; - })); - } - private _watchForOpacityChanges() { this._opacity.pipe(takeUntil(this._destroyed)).subscribe(opacity => { - if (opacity) { + if (opacity != null) { this._assertInitialized(); this.groundOverlay.setOpacity(opacity); } diff --git a/tools/public_api_guard/google-maps/google-maps.d.ts b/tools/public_api_guard/google-maps/google-maps.d.ts index 595b08738c7c..a039a1a5d6cc 100644 --- a/tools/public_api_guard/google-maps/google-maps.d.ts +++ b/tools/public_api_guard/google-maps/google-maps.d.ts @@ -113,7 +113,8 @@ export declare class MapCircle implements OnInit, OnDestroy { } export declare class MapGroundOverlay implements OnInit, OnDestroy { - bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral; + get bounds(): google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral; + set bounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral); clickable: boolean; groundOverlay?: google.maps.GroundOverlay; mapClick: Observable;