diff --git a/packages/core/services/maps-api-loader/lazy-maps-api-loader.spec.ts b/packages/core/services/maps-api-loader/lazy-maps-api-loader.spec.ts index 5f3e3b502..274f753a3 100644 --- a/packages/core/services/maps-api-loader/lazy-maps-api-loader.spec.ts +++ b/packages/core/services/maps-api-loader/lazy-maps-api-loader.spec.ts @@ -8,46 +8,66 @@ import {MapsAPILoader} from './maps-api-loader'; describe('Service: LazyMapsAPILoader', () => { let documentRef: DocumentRef; let doc: any; - let windowRef: any; + let windowRef: WindowRef; + let windowObj: any; beforeEach(() => { - doc = jasmine.createSpyObj('Document', ['createElement']); + doc = jasmine.createSpyObj('Document', ['createElement', 'getElementById']); + (doc.getElementById).and.returnValue(null); + doc.body = jasmine.createSpyObj('body', ['appendChild']); documentRef = jasmine.createSpyObj('Document', ['getNativeDocument']); - (documentRef.getNativeDocument).and.returnValue(doc); - windowRef = {}; - }); + (documentRef.getNativeDocument).and.returnValue(doc); + + windowRef = jasmine.createSpyObj('windowRef', ['getNativeWindow']); + windowObj = {}; + (windowRef.getNativeWindow).and.returnValue(windowObj); - it('should create the default script URL', () => { TestBed.configureTestingModule({ providers: [ {provide: MapsAPILoader, useClass: LazyMapsAPILoader}, - {provide: WindowRef, useValue: windowRef}, {provide: DocumentRef, useValue: documentRef} + {provide: WindowRef, useValue: windowRef}, + {provide: DocumentRef, useValue: documentRef} ] }); + }); - inject([MapsAPILoader], (loader: LazyMapsAPILoader) => { + it('should create the default script URL', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => { interface Script { src?: string; async?: boolean; defer?: boolean; type?: string; + id?: string; } const scriptElem: Script = {}; (doc.createElement).and.returnValue(scriptElem); - doc.body = jasmine.createSpyObj('body', ['appendChild']); loader.load(); - expect(doc.createElement).toHaveBeenCalled(); + expect(doc.createElement).toHaveBeenCalledWith('script'); expect(scriptElem.type).toEqual('text/javascript'); expect(scriptElem.async).toEqual(true); expect(scriptElem.defer).toEqual(true); expect(scriptElem.src).toBeDefined(); + expect(scriptElem.id).toEqual('agmGoogleMapsApiScript'); expect(scriptElem.src).toContain('https://maps.googleapis.com/maps/api/js'); expect(scriptElem.src).toContain('v=3'); - expect(scriptElem.src).toContain('callback=angular2GoogleMapsLazyMapsAPILoader'); + expect(scriptElem.src).toContain('callback=agmLazyMapsAPILoader'); expect(doc.body.appendChild).toHaveBeenCalledWith(scriptElem); - }); - }); + })); + + it('should not append a second script to body when theres already one with the fixed ID', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => { + (doc.getElementById).and.returnValue(document.createElement('script')); + loader.load(); + expect(doc.body.appendChild).not.toHaveBeenCalledWith(); + })); + + it('should not append a second script to body when window.google.maps is defined', inject([MapsAPILoader], (loader: LazyMapsAPILoader) => { + windowObj.google = { + maps: {} + }; + loader.load(); + expect(doc.body.appendChild).not.toHaveBeenCalledWith(); + })); it('should load the script via http when provided', () => { const lazyLoadingConf: @@ -56,7 +76,8 @@ describe('Service: LazyMapsAPILoader', () => { TestBed.configureTestingModule({ providers: [ {provide: MapsAPILoader, useClass: LazyMapsAPILoader}, - {provide: WindowRef, useValue: windowRef}, {provide: DocumentRef, useValue: documentRef}, + {provide: WindowRef, useValue: windowRef}, + {provide: DocumentRef, useValue: documentRef}, {provide: LAZY_MAPS_API_CONFIG, useValue: lazyLoadingConf} ] }); @@ -70,13 +91,10 @@ describe('Service: LazyMapsAPILoader', () => { } const scriptElem: Script = {}; (doc.createElement).and.returnValue(scriptElem); - doc.body = jasmine.createSpyObj('body', ['appendChild']); loader.load(); expect(doc.createElement).toHaveBeenCalled(); expect(scriptElem.src).toContain('http://maps.googleapis.com/maps/api/js'); - expect(scriptElem.src).toContain('v=3'); - expect(scriptElem.src).toContain('callback=angular2GoogleMapsLazyMapsAPILoader'); expect(doc.body.appendChild).toHaveBeenCalledWith(scriptElem); }); }); diff --git a/packages/core/services/maps-api-loader/lazy-maps-api-loader.ts b/packages/core/services/maps-api-loader/lazy-maps-api-loader.ts index c05212f2a..ba7a37a72 100644 --- a/packages/core/services/maps-api-loader/lazy-maps-api-loader.ts +++ b/packages/core/services/maps-api-loader/lazy-maps-api-loader.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable, InjectionToken} from '@angular/core'; +import {Inject, Injectable, InjectionToken, Optional} from '@angular/core'; import {DocumentRef, WindowRef} from '../../utils/browser-globals'; @@ -14,7 +14,7 @@ export enum GoogleMapsScriptProtocol { * Token for the config of the LazyMapsAPILoader. Please provide an object of type {@link * LazyMapsAPILoaderConfig}. */ -export const LAZY_MAPS_API_CONFIG = new InjectionToken('angular-google-maps LAZY_MAPS_API_CONFIG'); +export const LAZY_MAPS_API_CONFIG = new InjectionToken('angular-google-maps LAZY_MAPS_API_CONFIG'); /** * Configuration for the {@link LazyMapsAPILoader}. @@ -84,8 +84,9 @@ export class LazyMapsAPILoader extends MapsAPILoader { protected _config: LazyMapsAPILoaderConfigLiteral; protected _windowRef: WindowRef; protected _documentRef: DocumentRef; + protected readonly _SCRIPT_ID: string = 'agmGoogleMapsApiScript'; - constructor(@Inject(LAZY_MAPS_API_CONFIG) config: any, w: WindowRef, d: DocumentRef) { + constructor(@Optional() @Inject(LAZY_MAPS_API_CONFIG) config: any = null, w: WindowRef, d: DocumentRef) { super(); this._config = config || {}; this._windowRef = w; @@ -99,6 +100,11 @@ export class LazyMapsAPILoader extends MapsAPILoader { return Promise.resolve(); } + if (this._documentRef.getNativeDocument().getElementById(this._SCRIPT_ID)) { + // this can happen in HMR situations or Stackblitz.io editors. + return Promise.resolve(); + } + if (this._scriptLoadingPromise) { return this._scriptLoadingPromise; } @@ -107,7 +113,8 @@ export class LazyMapsAPILoader extends MapsAPILoader { script.type = 'text/javascript'; script.async = true; script.defer = true; - const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`; + script.id = this._SCRIPT_ID; + const callbackName: string = `agmLazyMapsAPILoader`; script.src = this._getScriptSrc(callbackName); this._scriptLoadingPromise = new Promise((resolve: Function, reject: Function) => {