From a495906e867114c3794a3a5cecaf64a58ab30f07 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 11 May 2020 20:45:13 +0200 Subject: [PATCH] fix(google-maps): allow for ground overlay image to be changed As things are set up at the moment, changing the ground overlay's `url` won't do anything. These changes add some extra logic to update the URL and to tell the map to re-render. --- src/dev-app/google-map/google-map-demo.html | 12 +++++++ src/dev-app/google-map/google-map-demo.ts | 16 ++++++++- .../map-ground-overlay.spec.ts | 30 ++++++++++++++-- .../map-ground-overlay/map-ground-overlay.ts | 34 ++++++++++++++----- .../testing/fake-google-map-utils.ts | 3 ++ .../google-maps/google-maps.d.ts | 2 +- 6 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/dev-app/google-map/google-map-demo.html b/src/dev-app/google-map/google-map-demo.html index c0b7c03a4d40..68783e384dd4 100644 --- a/src/dev-app/google-map/google-map-demo.html +++ b/src/dev-app/google-map/google-map-demo.html @@ -101,4 +101,16 @@ +
+ +
+ diff --git a/src/dev-app/google-map/google-map-demo.ts b/src/dev-app/google-map/google-map-demo.ts index 1e3c234daaf9..faf3adb5752f 100644 --- a/src/dev-app/google-map/google-map-demo.ts +++ b/src/dev-app/google-map/google-map-demo.ts @@ -66,7 +66,17 @@ export class GoogleMapDemo { circleOptions: google.maps.CircleOptions = {center: CIRCLE_CENTER, radius: CIRCLE_RADIUS, strokeColor: 'grey', strokeOpacity: 0.8}; isGroundOverlayDisplayed = false; - groundOverlayUrl = 'https://angular.io/assets/images/logos/angular/angular.svg'; + groundOverlayImages = [ + { + title: 'Red logo', + url: 'https://angular.io/assets/images/logos/angular/angular.svg' + }, + { + title: 'Black logo', + url: 'https://angular.io/assets/images/logos/angular/angular_solidBlack.svg' + } + ]; + groundOverlayUrl = this.groundOverlayImages[0].url; groundOverlayBounds = RECTANGLE_BOUNDS; mapTypeId: google.maps.MapTypeId; @@ -149,4 +159,8 @@ export class GoogleMapDemo { toggleGroundOverlayDisplay() { this.isGroundOverlayDisplayed = !this.isGroundOverlayDisplayed; } + + groundOverlayUrlChanged(event: Event) { + this.groundOverlayUrl = (event.target as HTMLSelectElement).value; + } } 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 db74db3ac8bc..a5659c9b9cc0 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 @@ -57,11 +57,11 @@ describe('MapGroundOverlay', () => { expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); }); - it('has an error if required url or bounds are not provided', () => { + it('has an error if required bounds are not provided', () => { expect(() => { const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); - }).toThrow(new Error('An image url is required')); + }).toThrow(new Error('Image bounds are required')); }); it('exposes methods that provide information about the Ground Overlay', () => { @@ -119,6 +119,32 @@ describe('MapGroundOverlay', () => { expect(addSpy).toHaveBeenCalledWith('dblclick', jasmine.any(Function)); subscription.unsubscribe(); }); + + it('should be able to change the image after init', () => { + const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); + const groundOverlayConstructorSpy = + createGroundOverlayConstructorSpy(groundOverlaySpy).and.callThrough(); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.url = url; + fixture.componentInstance.bounds = bounds; + fixture.componentInstance.clickable = clickable; + fixture.componentInstance.opacity = opacity; + fixture.detectChanges(); + + expect(groundOverlayConstructorSpy).toHaveBeenCalledWith(url, bounds, groundOverlayOptions); + expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); + + groundOverlaySpy.setMap.calls.reset(); + fixture.componentInstance.url = 'foo.png'; + fixture.detectChanges(); + + expect(groundOverlaySpy.set).toHaveBeenCalledWith('url', 'foo.png'); + expect(groundOverlaySpy.setMap).toHaveBeenCalledTimes(2); + expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(null); + expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); + }); + }); @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 2fe4b67c4c97..1933b64117f8 100644 --- a/src/google-maps/map-ground-overlay/map-ground-overlay.ts +++ b/src/google-maps/map-ground-overlay/map-ground-overlay.ts @@ -28,6 +28,7 @@ export class MapGroundOverlay implements OnInit, OnDestroy { private _eventManager = new MapEventManager(this._ngZone); private readonly _opacity = new BehaviorSubject(1); + private readonly _url = new BehaviorSubject(''); private readonly _destroyed = new Subject(); /** @@ -37,13 +38,19 @@ export class MapGroundOverlay implements OnInit, OnDestroy { */ groundOverlay?: google.maps.GroundOverlay; - @Input() url!: string; // Asserted in ngOnInit. - + /** URL of the image that will be shown in the overlay. */ @Input() - bounds!: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral; // Asserted in ngOnInit. + set url(url: string) { + this._url.next(url); + } - @Input() clickable = false; + /** Bounds for the overlay. */ + @Input() bounds: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral; + /** Whether the overlay is clickable */ + @Input() clickable: boolean = false; + + /** Opacity of the overlay. */ @Input() set opacity(opacity: number) { this._opacity.next(opacity); @@ -69,9 +76,6 @@ export class MapGroundOverlay implements OnInit, OnDestroy { constructor(private readonly _map: GoogleMap, private readonly _ngZone: NgZone) {} ngOnInit() { - if (!this.url) { - throw Error('An image url is required'); - } if (!this.bounds) { throw Error('Image bounds are required'); } @@ -81,7 +85,8 @@ export class MapGroundOverlay implements OnInit, OnDestroy { // 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, this.bounds, options); + this.groundOverlay = + new google.maps.GroundOverlay(this._url.getValue(), this.bounds, options); }); this._assertInitialized(); this.groundOverlay.setMap(this._map.googleMap!); @@ -89,6 +94,7 @@ export class MapGroundOverlay implements OnInit, OnDestroy { }); this._watchForOpacityChanges(); + this._watchForUrlChanges(); } } @@ -150,6 +156,18 @@ export class MapGroundOverlay implements OnInit, OnDestroy { }); } + private _watchForUrlChanges() { + this._url.pipe(takeUntil(this._destroyed)).subscribe(url => { + this._assertInitialized(); + const overlay = this.groundOverlay; + overlay.set('url', url); + + // Google Maps only redraws the overlay if we re-set the map. + overlay.setMap(null); + overlay.setMap(this._map.googleMap!); + }); + } + private _assertInitialized(): asserts this is {groundOverlay: google.maps.GroundOverlay} { if (!this._map.googleMap) { throw Error( diff --git a/src/google-maps/testing/fake-google-map-utils.ts b/src/google-maps/testing/fake-google-map-utils.ts index 0ba94b417ccf..a1f53e176d84 100644 --- a/src/google-maps/testing/fake-google-map-utils.ts +++ b/src/google-maps/testing/fake-google-map-utils.ts @@ -241,6 +241,7 @@ export function createCircleConstructorSpy(circleSpy: jasmine.SpyObj { + const values: {[key: string]: any} = {url}; const groundOverlaySpy = jasmine.createSpyObj('google.maps.GroundOverlay', [ 'addListener', 'getBounds', @@ -248,8 +249,10 @@ export function createGroundOverlaySpy( 'getUrl', 'setMap', 'setOpacity', + 'set', ]); groundOverlaySpy.addListener.and.returnValue({remove: () => {}}); + groundOverlaySpy.set.and.callFake((key: string, value: any) => values[key] = value); return groundOverlaySpy; } 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 3420cbc11d95..a7953b537b0b 100644 --- a/tools/public_api_guard/google-maps/google-maps.d.ts +++ b/tools/public_api_guard/google-maps/google-maps.d.ts @@ -94,7 +94,7 @@ export declare class MapGroundOverlay implements OnInit, OnDestroy { mapClick: Observable; mapDblclick: Observable; set opacity(opacity: number); - url: string; + set url(url: string); constructor(_map: GoogleMap, _ngZone: NgZone); getBounds(): google.maps.LatLngBounds; getOpacity(): number;