Skip to content

Commit 0ced9c2

Browse files
committed
feat(google-maps): expose the underlying Google Maps objects.
Expose the underlying Google Maps objects so they can be interacted with when it's necessary to do something unimplemented by angular/google-maps. As part of this change, update DefinitelyTyped so that we do not need a custom map object. Also standardize how underlying objects interact with the components and updates new components to work correctly when loading from the server.
1 parent 1ba8b26 commit 0ced9c2

File tree

13 files changed

+374
-207
lines changed

13 files changed

+374
-207
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"@angular/elements": "^9.0.1",
5252
"@angular/forms": "^9.0.1",
5353
"@angular/platform-browser": "^9.0.1",
54-
"@types/googlemaps": "^3.37.0",
54+
"@types/googlemaps": "^3.39.3",
5555
"@types/youtube": "^0.0.38",
5656
"@webcomponents/custom-elements": "^1.1.0",
5757
"core-js": "^2.6.9",

src/google-maps/google-map/google-map.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
DEFAULT_OPTIONS,
1414
DEFAULT_WIDTH,
1515
GoogleMap,
16-
UpdatedGoogleMap
1716
} from './google-map';
1817

1918
/** Represents boundaries of a map to be used in tests. */
@@ -32,7 +31,7 @@ const testPosition: google.maps.LatLngLiteral = {
3231

3332
describe('GoogleMap', () => {
3433
let mapConstructorSpy: jasmine.Spy;
35-
let mapSpy: jasmine.SpyObj<UpdatedGoogleMap>;
34+
let mapSpy: jasmine.SpyObj<google.maps.Map>;
3635

3736
beforeEach(async(() => {
3837
TestBed.configureTestingModule({

src/google-maps/google-map/google-map.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
7474
private readonly _zoom = new BehaviorSubject<number|undefined>(undefined);
7575
private readonly _destroy = new Subject<void>();
7676
private _mapEl: HTMLElement;
77-
_googleMap: UpdatedGoogleMap;
77+
78+
/**
79+
* The underlying google.maps.Map object
80+
*
81+
* See developers.google.com/maps/documentation/javascript/reference/map#Map
82+
*/
83+
googleMap?: google.maps.Map;
7884

7985
/** Whether we're currently rendering inside a browser. */
8086
_isBrowser: boolean;
@@ -258,8 +264,8 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
258264
this._setSize();
259265
this._googleMapChanges = this._initializeMap(this._combineOptions());
260266
this._googleMapChanges.subscribe((googleMap: google.maps.Map) => {
261-
this._googleMap = googleMap as UpdatedGoogleMap;
262-
this._eventManager.setTarget(this._googleMap);
267+
this.googleMap = googleMap;
268+
this._eventManager.setTarget(this.googleMap);
263269
});
264270

265271
this._watchForOptionsChanges();
@@ -282,7 +288,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
282288
bounds: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral,
283289
padding?: number|google.maps.Padding) {
284290
this._assertInitialized();
285-
this._googleMap.fitBounds(bounds, padding);
291+
this.googleMap!.fitBounds(bounds, padding);
286292
}
287293

288294
/**
@@ -291,7 +297,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
291297
*/
292298
panBy(x: number, y: number) {
293299
this._assertInitialized();
294-
this._googleMap.panBy(x, y);
300+
this.googleMap!.panBy(x, y);
295301
}
296302

297303
/**
@@ -300,7 +306,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
300306
*/
301307
panTo(latLng: google.maps.LatLng|google.maps.LatLngLiteral) {
302308
this._assertInitialized();
303-
this._googleMap.panTo(latLng);
309+
this.googleMap!.panTo(latLng);
304310
}
305311

306312
/**
@@ -311,7 +317,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
311317
latLngBounds: google.maps.LatLngBounds|google.maps.LatLngBoundsLiteral,
312318
padding?: number|google.maps.Padding) {
313319
this._assertInitialized();
314-
this._googleMap.panToBounds(latLngBounds, padding);
320+
this.googleMap!.panToBounds(latLngBounds, padding);
315321
}
316322

317323
/**
@@ -320,7 +326,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
320326
*/
321327
getBounds(): google.maps.LatLngBounds|null {
322328
this._assertInitialized();
323-
return this._googleMap.getBounds() || null;
329+
return this.googleMap!.getBounds() || null;
324330
}
325331

326332
/**
@@ -329,7 +335,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
329335
*/
330336
getCenter(): google.maps.LatLng {
331337
this._assertInitialized();
332-
return this._googleMap.getCenter();
338+
return this.googleMap!.getCenter();
333339
}
334340

335341
/**
@@ -338,7 +344,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
338344
*/
339345
getClickableIcons(): boolean {
340346
this._assertInitialized();
341-
return this._googleMap.getClickableIcons();
347+
return this.googleMap!.getClickableIcons();
342348
}
343349

344350
/**
@@ -347,7 +353,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
347353
*/
348354
getHeading(): number {
349355
this._assertInitialized();
350-
return this._googleMap.getHeading();
356+
return this.googleMap!.getHeading();
351357
}
352358

353359
/**
@@ -356,7 +362,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
356362
*/
357363
getMapTypeId(): google.maps.MapTypeId|string {
358364
this._assertInitialized();
359-
return this._googleMap.getMapTypeId();
365+
return this.googleMap!.getMapTypeId();
360366
}
361367

362368
/**
@@ -365,7 +371,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
365371
*/
366372
getProjection(): google.maps.Projection|null {
367373
this._assertInitialized();
368-
return this._googleMap.getProjection();
374+
return this.googleMap!.getProjection();
369375
}
370376

371377
/**
@@ -374,7 +380,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
374380
*/
375381
getStreetView(): google.maps.StreetViewPanorama {
376382
this._assertInitialized();
377-
return this._googleMap.getStreetView();
383+
return this.googleMap!.getStreetView();
378384
}
379385

380386
/**
@@ -383,7 +389,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
383389
*/
384390
getTilt(): number {
385391
this._assertInitialized();
386-
return this._googleMap.getTilt();
392+
return this.googleMap!.getTilt();
387393
}
388394

389395
/**
@@ -392,7 +398,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
392398
*/
393399
getZoom(): number {
394400
this._assertInitialized();
395-
return this._googleMap.getZoom();
401+
return this.googleMap!.getZoom();
396402
}
397403

398404
/**
@@ -401,7 +407,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
401407
*/
402408
get controls(): Array<google.maps.MVCArray<Node>> {
403409
this._assertInitialized();
404-
return this._googleMap.controls;
410+
return this.googleMap!.controls;
405411
}
406412

407413
/**
@@ -410,7 +416,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
410416
*/
411417
get data(): google.maps.Data {
412418
this._assertInitialized();
413-
return this._googleMap.data;
419+
return this.googleMap!.data;
414420
}
415421

416422
/**
@@ -419,7 +425,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
419425
*/
420426
get mapTypes(): google.maps.MapTypeRegistry {
421427
this._assertInitialized();
422-
return this._googleMap.mapTypes;
428+
return this.googleMap!.mapTypes;
423429
}
424430

425431
/**
@@ -428,7 +434,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
428434
*/
429435
get overlayMapTypes(): google.maps.MVCArray<google.maps.MapType> {
430436
this._assertInitialized();
431-
return this._googleMap.overlayMapTypes;
437+
return this.googleMap!.overlayMapTypes;
432438
}
433439

434440
private _setSize() {
@@ -495,7 +501,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
495501

496502
/** Asserts that the map has been initialized. */
497503
private _assertInitialized() {
498-
if (!this._googleMap) {
504+
if (!this.googleMap) {
499505
throw Error('Cannot access Google Map information before the API has been initialized. ' +
500506
'Please wait for the API to load before trying to interact with it.');
501507
}

src/google-maps/map-info-window/map-info-window.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe('MapInfoWindow', () => {
112112

113113
it('exposes methods that change the configuration of the info window', () => {
114114
const fakeMarker = {} as unknown as google.maps.Marker;
115-
const fakeMarkerComponent = {_marker: fakeMarker} as unknown as MapMarker;
115+
const fakeMarkerComponent = {marker: fakeMarker} as unknown as MapMarker;
116116
const infoWindowSpy = createInfoWindowSpy({});
117117
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
118118

src/google-maps/map-info-window/map-info-window.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,22 @@ import {
1313
Directive,
1414
ElementRef,
1515
Input,
16+
NgZone,
1617
OnDestroy,
1718
OnInit,
1819
Output,
19-
NgZone,
2020
} from '@angular/core';
2121
import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs';
22-
import {map, takeUntil} from 'rxjs/operators';
22+
import {map, take, takeUntil} from 'rxjs/operators';
2323

2424
import {GoogleMap} from '../google-map/google-map';
25-
import {MapMarker} from '../map-marker/map-marker';
2625
import {MapEventManager} from '../map-event-manager';
26+
import {MapMarker} from '../map-marker/map-marker';
2727

2828
/**
2929
* Angular component that renders a Google Maps info window via the Google Maps JavaScript API.
30-
* @see developers.google.com/maps/documentation/javascript/reference/info-window
30+
*
31+
* See developers.google.com/maps/documentation/javascript/reference/info-window
3132
*/
3233
@Directive({
3334
selector: 'map-info-window',
@@ -39,7 +40,13 @@ export class MapInfoWindow implements OnInit, OnDestroy {
3940
private readonly _position =
4041
new BehaviorSubject<google.maps.LatLngLiteral|google.maps.LatLng|undefined>(undefined);
4142
private readonly _destroy = new Subject<void>();
42-
private _infoWindow?: google.maps.InfoWindow;
43+
44+
/**
45+
* Underlying google.maps.InfoWindow
46+
*
47+
* See developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow
48+
*/
49+
infoWindow?: google.maps.InfoWindow;
4350

4451
@Input()
4552
set options(options: google.maps.InfoWindowOptions) {
@@ -93,20 +100,21 @@ export class MapInfoWindow implements OnInit, OnDestroy {
93100

94101
ngOnInit() {
95102
if (this._googleMap._isBrowser) {
96-
this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => {
97-
if (this._infoWindow) {
98-
this._infoWindow.setOptions(options);
99-
} else {
100-
// Create the object outside the zone so its events don't trigger change detection.
101-
// We'll bring it back in inside the `MapEventManager` only for the events that the
102-
// user has subscribed to.
103-
this._ngZone.runOutsideAngular(() => {
104-
this._infoWindow = new google.maps.InfoWindow(options);
105-
});
106-
107-
this._eventManager.setTarget(this._infoWindow);
108-
}
103+
const combinedOptionsChanges = this._combineOptions();
104+
105+
combinedOptionsChanges.pipe(take(1)).subscribe(options => {
106+
// Create the object outside the zone so its events don't trigger change detection.
107+
// We'll bring it back in inside the `MapEventManager` only for the events that the
108+
// user has subscribed to.
109+
this._ngZone.runOutsideAngular(() => {
110+
this.infoWindow = new google.maps.InfoWindow(options);
111+
});
112+
113+
this._eventManager.setTarget(this.infoWindow);
109114
});
115+
116+
this._watchForOptionsChanges();
117+
this._watchForPositionChanges();
110118
}
111119
}
112120

@@ -121,17 +129,17 @@ export class MapInfoWindow implements OnInit, OnDestroy {
121129
* See developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.close
122130
*/
123131
close() {
124-
if (this._infoWindow) {
125-
this._infoWindow.close();
126-
}
132+
this._assertInitialized();
133+
this.infoWindow!.close();
127134
}
128135

129136
/**
130137
* See
131138
* developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.getContent
132139
*/
133140
getContent(): string|Node {
134-
return this._infoWindow ? this._infoWindow.getContent() : '';
141+
this._assertInitialized();
142+
return this.infoWindow!.getContent();
135143
}
136144

137145
/**
@@ -140,27 +148,28 @@ export class MapInfoWindow implements OnInit, OnDestroy {
140148
* #InfoWindow.getPosition
141149
*/
142150
getPosition(): google.maps.LatLng|null {
143-
return this._infoWindow ? this._infoWindow.getPosition() : null;
151+
this._assertInitialized();
152+
return this.infoWindow!.getPosition();
144153
}
145154

146155
/**
147156
* See
148157
* developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.getZIndex
149158
*/
150159
getZIndex(): number {
151-
return this._infoWindow ? this._infoWindow.getZIndex() : -1;
160+
this._assertInitialized();
161+
return this.infoWindow!.getZIndex();
152162
}
153163

154164
/**
155165
* Opens the MapInfoWindow using the provided MapMarker as the anchor. If the anchor is not set,
156166
* then the position property of the options input is used instead.
157167
*/
158168
open(anchor?: MapMarker) {
159-
const marker = anchor ? anchor._marker : undefined;
160-
if (this._googleMap._googleMap && this._infoWindow) {
161-
this._elementRef.nativeElement.style.display = '';
162-
this._infoWindow!.open(this._googleMap._googleMap, marker);
163-
}
169+
this._assertInitialized();
170+
const marker = anchor ? anchor.marker : undefined;
171+
this._elementRef.nativeElement.style.display = '';
172+
this.infoWindow!.open(this._googleMap.googleMap, marker);
164173
}
165174

166175
private _combineOptions(): Observable<google.maps.InfoWindowOptions> {
@@ -173,4 +182,34 @@ export class MapInfoWindow implements OnInit, OnDestroy {
173182
return combinedOptions;
174183
}));
175184
}
185+
186+
private _watchForOptionsChanges() {
187+
this._options.pipe(takeUntil(this._destroy)).subscribe(options => {
188+
this._assertInitialized();
189+
this.infoWindow!.setOptions(options);
190+
});
191+
}
192+
193+
private _watchForPositionChanges() {
194+
this._position.pipe(takeUntil(this._destroy)).subscribe(position => {
195+
if (position) {
196+
this._assertInitialized();
197+
this.infoWindow!.setPosition(position);
198+
}
199+
});
200+
}
201+
202+
private _assertInitialized() {
203+
if (!this._googleMap.googleMap) {
204+
throw Error(
205+
'Cannot access Google Map information before the API has been initialized. ' +
206+
'Please wait for the API to load before trying to interact with it.');
207+
}
208+
if (!this.infoWindow) {
209+
throw Error(
210+
'Cannot interact with a Google Map Info Window before it has been ' +
211+
'initialized. Please wait for the Info Window to load before trying to interact with ' +
212+
'it.');
213+
}
214+
}
176215
}

0 commit comments

Comments
 (0)