Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit 07de5a4

Browse files
authored
fix(LazyMapsAPILoader): multiple google maps api scripts on page
This prevents that we have multiple create script tags for the google maps api. Fixes #315 Fixes #775 Fixes #1260
1 parent 2563cae commit 07de5a4

File tree

2 files changed

+46
-21
lines changed

2 files changed

+46
-21
lines changed

packages/core/services/maps-api-loader/lazy-maps-api-loader.spec.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,46 +8,66 @@ import {MapsAPILoader} from './maps-api-loader';
88
describe('Service: LazyMapsAPILoader', () => {
99
let documentRef: DocumentRef;
1010
let doc: any;
11-
let windowRef: any;
11+
let windowRef: WindowRef;
12+
let windowObj: any;
1213

1314
beforeEach(() => {
14-
doc = jasmine.createSpyObj<DocumentRef>('Document', ['createElement']);
15+
doc = jasmine.createSpyObj<DocumentRef>('Document', ['createElement', 'getElementById']);
16+
(<jasmine.Spy>doc.getElementById).and.returnValue(null);
17+
doc.body = jasmine.createSpyObj('body', ['appendChild']);
1518
documentRef = jasmine.createSpyObj<DocumentRef>('Document', ['getNativeDocument']);
16-
(<any>documentRef.getNativeDocument).and.returnValue(doc);
17-
windowRef = {};
18-
});
19+
(<jasmine.Spy>documentRef.getNativeDocument).and.returnValue(doc);
20+
21+
windowRef = jasmine.createSpyObj<WindowRef>('windowRef', ['getNativeWindow']);
22+
windowObj = {};
23+
(<jasmine.Spy>windowRef.getNativeWindow).and.returnValue(windowObj);
1924

20-
it('should create the default script URL', () => {
2125
TestBed.configureTestingModule({
2226
providers: [
2327
{provide: MapsAPILoader, useClass: LazyMapsAPILoader},
24-
{provide: WindowRef, useValue: windowRef}, {provide: DocumentRef, useValue: documentRef}
28+
{provide: WindowRef, useValue: windowRef},
29+
{provide: DocumentRef, useValue: documentRef}
2530
]
2631
});
32+
});
2733

28-
inject([MapsAPILoader], (loader: LazyMapsAPILoader) => {
34+
it('should create the default script URL', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => {
2935
interface Script {
3036
src?: string;
3137
async?: boolean;
3238
defer?: boolean;
3339
type?: string;
40+
id?: string;
3441
}
3542
const scriptElem: Script = {};
3643
(<jasmine.Spy>doc.createElement).and.returnValue(scriptElem);
37-
doc.body = jasmine.createSpyObj('body', ['appendChild']);
3844

3945
loader.load();
40-
expect(doc.createElement).toHaveBeenCalled();
46+
expect(doc.createElement).toHaveBeenCalledWith('script');
4147
expect(scriptElem.type).toEqual('text/javascript');
4248
expect(scriptElem.async).toEqual(true);
4349
expect(scriptElem.defer).toEqual(true);
4450
expect(scriptElem.src).toBeDefined();
51+
expect(scriptElem.id).toEqual('agmGoogleMapsApiScript');
4552
expect(scriptElem.src).toContain('https://maps.googleapis.com/maps/api/js');
4653
expect(scriptElem.src).toContain('v=3');
47-
expect(scriptElem.src).toContain('callback=angular2GoogleMapsLazyMapsAPILoader');
54+
expect(scriptElem.src).toContain('callback=agmLazyMapsAPILoader');
4855
expect(doc.body.appendChild).toHaveBeenCalledWith(scriptElem);
49-
});
50-
});
56+
}));
57+
58+
it('should not append a second script to body when theres already one with the fixed ID', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => {
59+
(<jasmine.Spy>doc.getElementById).and.returnValue(document.createElement('script'));
60+
loader.load();
61+
expect(doc.body.appendChild).not.toHaveBeenCalledWith();
62+
}));
63+
64+
it('should not append a second script to body when window.google.maps is defined', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => {
65+
windowObj.google = {
66+
maps: {}
67+
};
68+
loader.load();
69+
expect(doc.body.appendChild).not.toHaveBeenCalledWith();
70+
}));
5171

5272
it('should load the script via http when provided', () => {
5373
const lazyLoadingConf:
@@ -56,7 +76,8 @@ describe('Service: LazyMapsAPILoader', () => {
5676
TestBed.configureTestingModule({
5777
providers: [
5878
{provide: MapsAPILoader, useClass: LazyMapsAPILoader},
59-
{provide: WindowRef, useValue: windowRef}, {provide: DocumentRef, useValue: documentRef},
79+
{provide: WindowRef, useValue: windowRef},
80+
{provide: DocumentRef, useValue: documentRef},
6081
{provide: LAZY_MAPS_API_CONFIG, useValue: lazyLoadingConf}
6182
]
6283
});
@@ -70,13 +91,10 @@ describe('Service: LazyMapsAPILoader', () => {
7091
}
7192
const scriptElem: Script = {};
7293
(<jasmine.Spy>doc.createElement).and.returnValue(scriptElem);
73-
doc.body = jasmine.createSpyObj('body', ['appendChild']);
7494

7595
loader.load();
7696
expect(doc.createElement).toHaveBeenCalled();
7797
expect(scriptElem.src).toContain('http://maps.googleapis.com/maps/api/js');
78-
expect(scriptElem.src).toContain('v=3');
79-
expect(scriptElem.src).toContain('callback=angular2GoogleMapsLazyMapsAPILoader');
8098
expect(doc.body.appendChild).toHaveBeenCalledWith(scriptElem);
8199
});
82100
});

packages/core/services/maps-api-loader/lazy-maps-api-loader.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Inject, Injectable, InjectionToken} from '@angular/core';
1+
import {Inject, Injectable, InjectionToken, Optional} from '@angular/core';
22

33
import {DocumentRef, WindowRef} from '../../utils/browser-globals';
44

@@ -14,7 +14,7 @@ export enum GoogleMapsScriptProtocol {
1414
* Token for the config of the LazyMapsAPILoader. Please provide an object of type {@link
1515
* LazyMapsAPILoaderConfig}.
1616
*/
17-
export const LAZY_MAPS_API_CONFIG = new InjectionToken('angular-google-maps LAZY_MAPS_API_CONFIG');
17+
export const LAZY_MAPS_API_CONFIG = new InjectionToken<LazyMapsAPILoaderConfigLiteral>('angular-google-maps LAZY_MAPS_API_CONFIG');
1818

1919
/**
2020
* Configuration for the {@link LazyMapsAPILoader}.
@@ -84,8 +84,9 @@ export class LazyMapsAPILoader extends MapsAPILoader {
8484
protected _config: LazyMapsAPILoaderConfigLiteral;
8585
protected _windowRef: WindowRef;
8686
protected _documentRef: DocumentRef;
87+
protected readonly _SCRIPT_ID: string = 'agmGoogleMapsApiScript';
8788

88-
constructor(@Inject(LAZY_MAPS_API_CONFIG) config: any, w: WindowRef, d: DocumentRef) {
89+
constructor(@Optional() @Inject(LAZY_MAPS_API_CONFIG) config: any = null, w: WindowRef, d: DocumentRef) {
8990
super();
9091
this._config = config || {};
9192
this._windowRef = w;
@@ -99,6 +100,11 @@ export class LazyMapsAPILoader extends MapsAPILoader {
99100
return Promise.resolve();
100101
}
101102

103+
if (this._documentRef.getNativeDocument().getElementById(this._SCRIPT_ID)) {
104+
// this can happen in HMR situations or Stackblitz.io editors.
105+
return Promise.resolve();
106+
}
107+
102108
if (this._scriptLoadingPromise) {
103109
return this._scriptLoadingPromise;
104110
}
@@ -107,7 +113,8 @@ export class LazyMapsAPILoader extends MapsAPILoader {
107113
script.type = 'text/javascript';
108114
script.async = true;
109115
script.defer = true;
110-
const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`;
116+
script.id = this._SCRIPT_ID;
117+
const callbackName: string = `agmLazyMapsAPILoader`;
111118
script.src = this._getScriptSrc(callbackName);
112119

113120
this._scriptLoadingPromise = new Promise<void>((resolve: Function, reject: Function) => {

0 commit comments

Comments
 (0)