Skip to content

Commit 8ad07ca

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 add7799 commit 8ad07ca

File tree

16 files changed

+409
-212
lines changed

16 files changed

+409
-212
lines changed

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,16 +309,24 @@ 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(async () => {
316+
this._initialize(await importLibrary<typeof google.maps.Map>('maps', 'Map'));
317+
});
318+
}
319319
}
320320
}
321321

322+
private _initialize(mapConstructor: typeof google.maps.Map) {
323+
this._ngZone.runOutsideAngular(() => {
324+
this.googleMap = new mapConstructor(this._mapEl, this._combineOptions());
325+
this._eventManager.setTarget(this.googleMap);
326+
this.mapInitialized.emit(this.googleMap);
327+
});
328+
}
329+
322330
ngOnDestroy() {
323331
this.mapInitialized.complete();
324332
this._eventManager.destroy();

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(async () => {
48+
this._initialize(
49+
...(await Promise.all([
50+
this._map._resolveMap(),
51+
importLibrary<typeof google.maps.BicyclingLayer>('maps', 'BicyclingLayer'),
52+
])),
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: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,25 +175,40 @@ 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(async () => {
182+
const [map, circleConstructor] = await Promise.all([
183+
this._map._resolveMap(),
184+
importLibrary<typeof google.maps.Circle>('maps', 'Circle'),
185+
]);
186+
this._initialize(map, circleConstructor, options);
187+
});
188+
}
194189
});
195190
}
196191

192+
private _initialize(
193+
map: google.maps.Map,
194+
circleConstructor: typeof google.maps.Circle,
195+
options: google.maps.CircleOptions,
196+
) {
197+
// Create the object outside the zone so its events don't trigger change detection.
198+
// We'll bring it back in inside the `MapEventManager` only for the events that the
199+
// user has subscribed to.
200+
this._ngZone.runOutsideAngular(() => {
201+
this.circle = new circleConstructor(options);
202+
this._assertInitialized();
203+
this.circle.setMap(map);
204+
this._eventManager.setTarget(this.circle);
205+
this.circleInitialized.emit(this.circle);
206+
this._watchForOptionsChanges();
207+
this._watchForCenterChanges();
208+
this._watchForRadiusChanges();
209+
});
210+
}
211+
197212
ngOnDestroy() {
198213
this._eventManager.destroy();
199214
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(async () => {
89+
this._initialize(
90+
...(await Promise.all([
91+
this._googleMap._resolveMap(),
92+
importLibrary<typeof google.maps.DirectionsRenderer>('routes', 'DirectionsRenderer'),
93+
])),
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-ground-overlay/map-ground-overlay.ts

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -117,34 +117,48 @@ export class MapGroundOverlay implements OnInit, OnDestroy {
117117
return;
118118
}
119119

120-
// Create the object outside the zone so its events don't trigger change detection.
121-
// We'll bring it back in inside the `MapEventManager` only for the events that the
122-
// user has subscribed to.
123-
this._ngZone.runOutsideAngular(async () => {
124-
const map = await this._map._resolveMap();
125-
const overlayConstructor =
126-
google.maps.GroundOverlay ||
127-
(await importLibrary<google.maps.GroundOverlay>('maps', 'GroundOverlay'));
128-
this.groundOverlay = new overlayConstructor(this._url.getValue(), bounds, {
129-
clickable: this.clickable,
130-
opacity: this._opacity.value,
120+
if (google.maps.GroundOverlay && this._map.googleMap) {
121+
this._initialize(this._map.googleMap, google.maps.GroundOverlay, bounds);
122+
} else {
123+
this._ngZone.runOutsideAngular(async () => {
124+
const [map, overlayConstructor] = await Promise.all([
125+
this._map._resolveMap(),
126+
importLibrary<typeof google.maps.GroundOverlay>('maps', 'GroundOverlay'),
127+
]);
128+
this._initialize(map, overlayConstructor, bounds);
131129
});
132-
this._assertInitialized();
133-
this.groundOverlay.setMap(map);
134-
this._eventManager.setTarget(this.groundOverlay);
135-
this.groundOverlayInitialized.emit(this.groundOverlay);
136-
137-
// We only need to set up the watchers once.
138-
if (!this._hasWatchers) {
139-
this._hasWatchers = true;
140-
this._watchForOpacityChanges();
141-
this._watchForUrlChanges();
142-
}
143-
});
130+
}
144131
});
145132
}
146133
}
147134

135+
private _initialize(
136+
map: google.maps.Map,
137+
overlayConstructor: typeof google.maps.GroundOverlay,
138+
bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral,
139+
) {
140+
// Create the object outside the zone so its events don't trigger change detection.
141+
// We'll bring it back in inside the `MapEventManager` only for the events that the
142+
// user has subscribed to.
143+
this._ngZone.runOutsideAngular(() => {
144+
this.groundOverlay = new overlayConstructor(this._url.getValue(), bounds, {
145+
clickable: this.clickable,
146+
opacity: this._opacity.value,
147+
});
148+
this._assertInitialized();
149+
this.groundOverlay.setMap(map);
150+
this._eventManager.setTarget(this.groundOverlay);
151+
this.groundOverlayInitialized.emit(this.groundOverlay);
152+
153+
// We only need to set up the watchers once.
154+
if (!this._hasWatchers) {
155+
this._hasWatchers = true;
156+
this._watchForOpacityChanges();
157+
this._watchForUrlChanges();
158+
}
159+
});
160+
}
161+
148162
ngOnDestroy() {
149163
this._eventManager.destroy();
150164
this._destroyed.next();

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -92,25 +92,39 @@ export class MapHeatmapLayer implements OnInit, OnChanges, OnDestroy {
9292
);
9393
}
9494

95-
// Create the object outside the zone so its events don't trigger change detection.
96-
// We'll bring it back in inside the `MapEventManager` only for the events that the
97-
// user has subscribed to.
98-
this._ngZone.runOutsideAngular(async () => {
99-
const map = await this._googleMap._resolveMap();
100-
const heatmapConstructor =
101-
google.maps.visualization?.HeatmapLayer ||
102-
(await importLibrary<google.maps.visualization.HeatmapLayer>(
103-
'visualization',
104-
'HeatmapLayer',
105-
));
106-
this.heatmap = new heatmapConstructor(this._combineOptions());
107-
this._assertInitialized();
108-
this.heatmap.setMap(map);
109-
this.heatmapInitialized.emit(this.heatmap);
110-
});
95+
if (google.maps.visualization?.HeatmapLayer && this._googleMap.googleMap) {
96+
this._initialize(this._googleMap.googleMap, google.maps.visualization.HeatmapLayer);
97+
} else {
98+
this._ngZone.runOutsideAngular(async () => {
99+
this._initialize(
100+
...(await Promise.all([
101+
this._googleMap._resolveMap(),
102+
importLibrary<typeof google.maps.visualization.HeatmapLayer>(
103+
'visualization',
104+
'HeatmapLayer',
105+
),
106+
])),
107+
);
108+
});
109+
}
111110
}
112111
}
113112

113+
private _initialize(
114+
map: google.maps.Map,
115+
heatmapConstructor: typeof google.maps.visualization.HeatmapLayer,
116+
) {
117+
// Create the object outside the zone so its events don't trigger change detection.
118+
// We'll bring it back in inside the `MapEventManager` only for the events that the
119+
// user has subscribed to.
120+
this._ngZone.runOutsideAngular(() => {
121+
this.heatmap = new heatmapConstructor(this._combineOptions());
122+
this._assertInitialized();
123+
this.heatmap.setMap(map);
124+
this.heatmapInitialized.emit(this.heatmap);
125+
});
126+
}
127+
114128
ngOnChanges(changes: SimpleChanges) {
115129
const {_data, heatmap} = this;
116130

0 commit comments

Comments
 (0)