Skip to content

Commit 72525c8

Browse files
committed
refactor(google-maps): reduce breaking changes from importLibrary introduction
Reworks the Google Maps components to reduce the amount of breakages due to the initialization now being asynchronous.
1 parent 3bc61a1 commit 72525c8

File tree

20 files changed

+504
-282
lines changed

20 files changed

+504
-282
lines changed

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -309,16 +309,26 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
309309
// Create the object outside the zone so its events don't trigger change detection.
310310
// We'll bring it back in inside the `MapEventManager` only for the events that the
311311
// user has subscribed to.
312-
this._ngZone.runOutsideAngular(async () => {
313-
const mapConstructor =
314-
google.maps.Map || (await importLibrary<google.maps.Map>('maps', 'Map'));
315-
this.googleMap = new mapConstructor(this._mapEl, this._combineOptions());
316-
this._eventManager.setTarget(this.googleMap);
317-
this.mapInitialized.emit(this.googleMap);
318-
});
312+
if (google.maps.Map) {
313+
this._initialize(google.maps.Map);
314+
} else {
315+
this._ngZone.runOutsideAngular(() => {
316+
importLibrary<typeof google.maps.Map>('maps', 'Map').then(mapConstructor =>
317+
this._initialize(mapConstructor),
318+
);
319+
});
320+
}
319321
}
320322
}
321323

324+
private _initialize(mapConstructor: typeof google.maps.Map) {
325+
this._ngZone.runOutsideAngular(() => {
326+
this.googleMap = new mapConstructor(this._mapEl, this._combineOptions());
327+
this._eventManager.setTarget(this.googleMap);
328+
this.mapInitialized.emit(this.googleMap);
329+
});
330+
}
331+
322332
ngOnDestroy() {
323333
this.mapInitialized.complete();
324334
this._eventManager.destroy();
@@ -489,8 +499,10 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
489499
}
490500

491501
/** Returns a promise that resolves when the map has been initialized. */
492-
async _resolveMap(): Promise<google.maps.Map> {
493-
return this.googleMap || this.mapInitialized.pipe(take(1)).toPromise();
502+
_resolveMap(): Promise<google.maps.Map> {
503+
return this.googleMap
504+
? Promise.resolve(this.googleMap)
505+
: this.mapInitialized.pipe(take(1)).toPromise();
494506
}
495507

496508
private _setSize() {

src/google-maps/import-library.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
*/
88

99
/** Imports a Google Maps library. */
10-
export async function importLibrary<T>(name: string, symbol: string): Promise<T> {
10+
export function importLibrary<T>(name: string, symbol: string): Promise<T> {
1111
// TODO(crisbeto): needs to cast to `any` to avoid some internal limitations around typings.
1212
// Should be cleaned up eventually.
13-
const library = await (window as any).google.maps.importLibrary(name);
14-
return library[symbol];
13+
return (window as any).google.maps.importLibrary(name).then((library: any) => library[symbol]);
1514
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,28 @@ export class MapBaseLayer implements OnInit, OnDestroy {
2626

2727
ngOnInit() {
2828
if (this._map._isBrowser) {
29-
this._ngZone.runOutsideAngular(async () => {
30-
const map = await this._map._resolveMap();
31-
await this._initializeObject();
32-
this._setMap(map);
29+
this._ngZone.runOutsideAngular(() => {
30+
this._initializeObject();
3331
});
32+
this._assertInitialized();
33+
this._setMap();
3434
}
3535
}
3636

3737
ngOnDestroy() {
3838
this._unsetMap();
3939
}
4040

41-
protected async _initializeObject() {}
42-
protected _setMap(_map: google.maps.Map) {}
41+
private _assertInitialized() {
42+
if (!this._map.googleMap) {
43+
throw Error(
44+
'Cannot access Google Map information before the API has been initialized. ' +
45+
'Please wait for the API to load before trying to interact with it.',
46+
);
47+
}
48+
}
49+
50+
protected _initializeObject() {}
51+
protected _setMap() {}
4352
protected _unsetMap() {}
4453
}

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

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
// Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265
1010
/// <reference types="google.maps" />
1111

12-
import {Directive, EventEmitter, Output} from '@angular/core';
12+
import {Directive, EventEmitter, NgZone, OnDestroy, OnInit, Output, inject} from '@angular/core';
1313

14-
import {MapBaseLayer} from '../map-base-layer';
1514
import {importLibrary} from '../import-library';
15+
import {GoogleMap} from '../google-map/google-map';
1616

1717
/**
1818
* Angular component that renders a Google Maps Bicycling Layer via the Google Maps JavaScript API.
@@ -24,7 +24,10 @@ import {importLibrary} from '../import-library';
2424
exportAs: 'mapBicyclingLayer',
2525
standalone: true,
2626
})
27-
export class MapBicyclingLayer extends MapBaseLayer {
27+
export class MapBicyclingLayer implements OnInit, OnDestroy {
28+
private _map = inject(GoogleMap);
29+
private _zone = inject(NgZone);
30+
2831
/**
2932
* The underlying google.maps.BicyclingLayer object.
3033
*
@@ -36,20 +39,33 @@ export class MapBicyclingLayer extends MapBaseLayer {
3639
@Output() readonly bicyclingLayerInitialized: EventEmitter<google.maps.BicyclingLayer> =
3740
new EventEmitter<google.maps.BicyclingLayer>();
3841

39-
protected override async _initializeObject() {
40-
const layerConstructor =
41-
google.maps.BicyclingLayer ||
42-
(await importLibrary<google.maps.BicyclingLayer>('maps', 'BicyclingLayer'));
43-
this.bicyclingLayer = new layerConstructor();
44-
this.bicyclingLayerInitialized.emit(this.bicyclingLayer);
42+
ngOnInit(): void {
43+
if (this._map._isBrowser) {
44+
if (google.maps.BicyclingLayer && this._map.googleMap) {
45+
this._initialize(this._map.googleMap, google.maps.BicyclingLayer);
46+
} else {
47+
this._zone.runOutsideAngular(() => {
48+
Promise.all([
49+
this._map._resolveMap(),
50+
importLibrary<typeof google.maps.BicyclingLayer>('maps', 'BicyclingLayer'),
51+
]).then(([map, layerConstructor]) => {
52+
this._initialize(map, layerConstructor);
53+
});
54+
});
55+
}
56+
}
4557
}
4658

47-
protected override _setMap(map: google.maps.Map) {
48-
this._assertLayerInitialized();
49-
this.bicyclingLayer.setMap(map);
59+
private _initialize(map: google.maps.Map, layerConstructor: typeof google.maps.BicyclingLayer) {
60+
this._zone.runOutsideAngular(() => {
61+
this.bicyclingLayer = new layerConstructor();
62+
this.bicyclingLayerInitialized.emit(this.bicyclingLayer);
63+
this._assertLayerInitialized();
64+
this.bicyclingLayer.setMap(map);
65+
});
5066
}
5167

52-
protected override _unsetMap() {
68+
ngOnDestroy() {
5369
this.bicyclingLayer?.setMap(null);
5470
}
5571

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

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,25 +175,41 @@ export class MapCircle implements OnInit, OnDestroy {
175175
this._combineOptions()
176176
.pipe(take(1))
177177
.subscribe(options => {
178-
// Create the object outside the zone so its events don't trigger change detection.
179-
// We'll bring it back in inside the `MapEventManager` only for the events that the
180-
// user has subscribed to.
181-
this._ngZone.runOutsideAngular(async () => {
182-
const map = await this._map._resolveMap();
183-
const circleConstructor =
184-
google.maps.Circle || (await importLibrary<google.maps.Circle>('maps', 'Circle'));
185-
this.circle = new circleConstructor(options);
186-
this._assertInitialized();
187-
this.circle.setMap(map);
188-
this._eventManager.setTarget(this.circle);
189-
this.circleInitialized.emit(this.circle);
190-
this._watchForOptionsChanges();
191-
this._watchForCenterChanges();
192-
this._watchForRadiusChanges();
193-
});
178+
if (google.maps.Circle && this._map.googleMap) {
179+
this._initialize(this._map.googleMap, google.maps.Circle, options);
180+
} else {
181+
this._ngZone.runOutsideAngular(() => {
182+
Promise.all([
183+
this._map._resolveMap(),
184+
importLibrary<typeof google.maps.Circle>('maps', 'Circle'),
185+
]).then(([map, circleConstructor]) => {
186+
this._initialize(map, circleConstructor, options);
187+
});
188+
});
189+
}
194190
});
195191
}
196192

193+
private _initialize(
194+
map: google.maps.Map,
195+
circleConstructor: typeof google.maps.Circle,
196+
options: google.maps.CircleOptions,
197+
) {
198+
// Create the object outside the zone so its events don't trigger change detection.
199+
// We'll bring it back in inside the `MapEventManager` only for the events that the
200+
// user has subscribed to.
201+
this._ngZone.runOutsideAngular(() => {
202+
this.circle = new circleConstructor(options);
203+
this._assertInitialized();
204+
this.circle.setMap(map);
205+
this._eventManager.setTarget(this.circle);
206+
this.circleInitialized.emit(this.circle);
207+
this._watchForOptionsChanges();
208+
this._watchForCenterChanges();
209+
this._watchForRadiusChanges();
210+
});
211+
}
212+
197213
ngOnDestroy() {
198214
this._eventManager.destroy();
199215
this._destroyed.next();

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,23 +82,37 @@ export class MapDirectionsRenderer implements OnInit, OnChanges, OnDestroy {
8282

8383
ngOnInit() {
8484
if (this._googleMap._isBrowser) {
85-
// Create the object outside the zone so its events don't trigger change detection.
86-
// We'll bring it back in inside the `MapEventManager` only for the events that the
87-
// user has subscribed to.
88-
this._ngZone.runOutsideAngular(async () => {
89-
const map = await this._googleMap._resolveMap();
90-
const rendererConstructor =
91-
google.maps.DirectionsRenderer ||
92-
(await importLibrary<google.maps.DirectionsRenderer>('routes', 'DirectionsRenderer'));
93-
this.directionsRenderer = new rendererConstructor(this._combineOptions());
94-
this._assertInitialized();
95-
this.directionsRenderer.setMap(map);
96-
this._eventManager.setTarget(this.directionsRenderer);
97-
this.directionsRendererInitialized.emit(this.directionsRenderer);
98-
});
85+
if (google.maps.DirectionsRenderer && this._googleMap.googleMap) {
86+
this._initialize(this._googleMap.googleMap, google.maps.DirectionsRenderer);
87+
} else {
88+
this._ngZone.runOutsideAngular(() => {
89+
Promise.all([
90+
this._googleMap._resolveMap(),
91+
importLibrary<typeof google.maps.DirectionsRenderer>('routes', 'DirectionsRenderer'),
92+
]).then(([map, rendererConstructor]) => {
93+
this._initialize(map, rendererConstructor);
94+
});
95+
});
96+
}
9997
}
10098
}
10199

100+
private _initialize(
101+
map: google.maps.Map,
102+
rendererConstructor: typeof google.maps.DirectionsRenderer,
103+
) {
104+
// Create the object outside the zone so its events don't trigger change detection.
105+
// We'll bring it back in inside the `MapEventManager` only for the events that the
106+
// user has subscribed to.
107+
this._ngZone.runOutsideAngular(() => {
108+
this.directionsRenderer = new rendererConstructor(this._combineOptions());
109+
this._assertInitialized();
110+
this.directionsRenderer.setMap(map);
111+
this._eventManager.setTarget(this.directionsRenderer);
112+
this.directionsRendererInitialized.emit(this.directionsRenderer);
113+
});
114+
}
115+
102116
ngOnChanges(changes: SimpleChanges) {
103117
if (this.directionsRenderer) {
104118
if (changes['options']) {

src/google-maps/map-directions-renderer/map-directions-service.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@ export class MapDirectionsService {
4848
});
4949
}
5050

51-
private async _getService(): Promise<google.maps.DirectionsService> {
51+
private _getService(): Promise<google.maps.DirectionsService> {
5252
if (!this._directionsService) {
53-
const serviceConstructor =
54-
google.maps.DirectionsService ||
55-
(await importLibrary<google.maps.DirectionsService>('routes', 'DirectionsService'));
56-
this._directionsService = new serviceConstructor();
53+
if (google.maps.DirectionsService) {
54+
this._directionsService = new google.maps.DirectionsService();
55+
} else {
56+
return importLibrary<typeof google.maps.DirectionsService>(
57+
'routes',
58+
'DirectionsService',
59+
).then(serviceConstructor => {
60+
this._directionsService = new serviceConstructor();
61+
return this._directionsService;
62+
});
63+
}
5764
}
5865

59-
return this._directionsService;
66+
return Promise.resolve(this._directionsService);
6067
}
6168
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,20 @@ export class MapGeocoder {
4444
});
4545
}
4646

47-
private async _getGeocoder(): Promise<google.maps.Geocoder> {
47+
private _getGeocoder(): Promise<google.maps.Geocoder> {
4848
if (!this._geocoder) {
49-
const geocoderConstructor =
50-
google.maps.Geocoder ||
51-
(await importLibrary<google.maps.Geocoder>('geocoding', 'Geocoder'));
52-
this._geocoder = new geocoderConstructor();
49+
if (google.maps.Geocoder) {
50+
this._geocoder = new google.maps.Geocoder();
51+
} else {
52+
return importLibrary<typeof google.maps.Geocoder>('geocoding', 'Geocoder').then(
53+
geocoderConstructor => {
54+
this._geocoder = new geocoderConstructor();
55+
return this._geocoder;
56+
},
57+
);
58+
}
5359
}
5460

55-
return this._geocoder;
61+
return Promise.resolve(this._geocoder);
5662
}
5763
}

0 commit comments

Comments
 (0)