import { Injectable, InjectionToken, Optional, Inject } from '@angular/core'; import { WindowRef, DocumentRef } from '../../utils/browser-globals'; import { MapsApiLoaderService } from './maps-api-loader.service'; export enum ProviderMapsScriptProtocol { HTTP = 1, HTTPS = 2, AUTO = 3 } export const LAZY_MAPS_API_CONFIG = new InjectionToken('LAZY_MAPS_API_CONFIG'); export interface LazyMapsAPILoaderConfigLiteral { /** * The Google Maps API Key (see: * https://developers.google.com/maps/documentation/javascript/get-api-key) */ apiKey?: string; clientId?: string; channel?: string; apiVersion?: string; hostAndPath?: string; protocol?: ProviderMapsScriptProtocol; libraries?: string[]; region?: string; language?: string; } @Injectable() export class LazyMapsApiLoaderService extends MapsApiLoaderService { protected _scriptLoadingPromise: Promise; protected _config: LazyMapsAPILoaderConfigLiteral; protected _windowRef: WindowRef; protected _documentRef: DocumentRef; protected readonly _SCRIPT_ID: string = 'bdbProviderMapsApiScript'; constructor(@Optional() @Inject(LAZY_MAPS_API_CONFIG) config: any = null, w: WindowRef, d: DocumentRef) { super(); this._config = config || {}; this._windowRef = w; this._documentRef = d; } load(): Promise { const window = this._windowRef.getNativeWindow(); if (window.google && window.google.maps) { // Google maps already loaded on the page. 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; } const script = this._documentRef.getNativeDocument().createElement('script'); script.type = 'text/javascript'; script.async = true; script.defer = true; script.id = this._SCRIPT_ID; const callbackName = 'bdbLazyMapsAPILoader'; script.src = this._getScriptSrc(callbackName); this._scriptLoadingPromise = new Promise((resolve: Function, reject: Function) => { (this._windowRef.getNativeWindow())[callbackName] = () => { resolve(); }; script.onerror = (error: Event) => { reject(error); }; }); this._documentRef.getNativeDocument().body.appendChild(script); return this._scriptLoadingPromise; } protected _getScriptSrc(callbackName: string): string { const protocolType: ProviderMapsScriptProtocol = (this._config && this._config.protocol) || ProviderMapsScriptProtocol.HTTPS; let protocol: string; switch (protocolType) { case ProviderMapsScriptProtocol.AUTO: protocol = ''; break; case ProviderMapsScriptProtocol.HTTP: protocol = 'http:'; break; case ProviderMapsScriptProtocol.HTTPS: protocol = 'https:'; break; } const hostAndPath: string = this._config.hostAndPath || 'maps.googleapis.com/maps/api/js'; const queryParams: { [key: string]: string | Array } = { v: this._config.apiVersion || '3', callback: callbackName, key: this._config.apiKey, client: this._config.clientId, channel: this._config.channel, libraries: this._config.libraries, region: this._config.region, language: this._config.language }; const params: string = Object.keys(queryParams) .filter((k: string) => queryParams[k] != null) .filter((k: string) => { // remove empty arrays return !Array.isArray(queryParams[k]) || (Array.isArray(queryParams[k]) && queryParams[k].length > 0); }) .map((k: string) => { // join arrays as comma seperated strings let i; i = queryParams[k]; if (Array.isArray(i)) { return { key: k, value: i.join(',') }; } return { key: k, value: queryParams[k] }; }) .map((entry: { key: string, value: string }) => { return `${entry.key}=${entry.value}`; }) .join('&'); return `${protocol}//${hostAndPath}?${params}`; } }