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

Commit 997aa80

Browse files
committed
feat(LazyMapsAPILoader): provide shortcut
Now, you can use a shortcut to configure the LazyMapsAPILoader: Before: ```typescript bootstrap(AppComponent, [ ANGULAR2_GOOGLE_MAPS_PROVIDERS, provide(LazyMapsAPILoaderConfig, {useFactory: () => { let config = new LazyMapsAPILoaderConfig(); config.apiKey = 'mykey'; return config; }}) ]) ``` After: ```typescript import {provide} from 'angular2/core'; import {provideLazyMapsAPILoaderConfig} from 'angular2-google-maps/core'; bootstrap(AppComponent, [ GOOGLE_MAPS_PROVIDERS, provideLazyMapsAPILoaderConfig({ apiKey: 'myKey' }) ]) ``` Closes #388 Closes #420
1 parent c5d5744 commit 997aa80

File tree

5 files changed

+142
-54
lines changed

5 files changed

+142
-54
lines changed

src/core/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import {Provider} from '@angular/core';
1+
import {provide} from '@angular/core';
22

33
import {LazyMapsAPILoader} from './services/maps-api-loader/lazy-maps-api-loader';
44
import {MapsAPILoader} from './services/maps-api-loader/maps-api-loader';
55

6+
import {BROWSER_GLOBALS_PROVIDERS} from './utils/browser-globals';
7+
68
// main modules
79
export * from './directives';
810
export * from './services';
911
export * from './events';
1012

1113
export const GOOGLE_MAPS_PROVIDERS: any[] = [
12-
new Provider(MapsAPILoader, {useClass: LazyMapsAPILoader}),
14+
...BROWSER_GLOBALS_PROVIDERS,
15+
provide(MapsAPILoader, {useClass: LazyMapsAPILoader}),
1316
];

src/core/services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export {GoogleMapsAPIWrapper} from './services/google-maps-api-wrapper';
22
export {InfoWindowManager} from './services/info-window-manager';
3-
export {GoogleMapsScriptProtocol, LazyMapsAPILoader, LazyMapsAPILoaderConfig} from './services/maps-api-loader/lazy-maps-api-loader';
3+
export {GoogleMapsScriptProtocol, LazyMapsAPILoader, LazyMapsAPILoaderConfig, LazyMapsAPILoaderConfigLiteral, provideLazyMapsAPILoaderConfig} from './services/maps-api-loader/lazy-maps-api-loader';
44
export {MapsAPILoader} from './services/maps-api-loader/maps-api-loader';
55
export {NoOpMapsAPILoader} from './services/maps-api-loader/noop-maps-api-loader';
66
export {MarkerManager} from './services/marker-manager';
Lines changed: 91 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {Injectable, Optional} from '@angular/core';
1+
import {Injectable, Optional, Provider, provide} from '@angular/core';
2+
23
import {MapsAPILoader} from './maps-api-loader';
34

45
export enum GoogleMapsScriptProtocol {
@@ -7,62 +8,81 @@ export enum GoogleMapsScriptProtocol {
78
AUTO
89
}
910

10-
export class LazyMapsAPILoaderConfig {
11+
/**
12+
* Config literal used to create an instance of LazyMapsAPILoaderConfig.
13+
*/
14+
export interface LazyMapsAPILoaderConfigLiteral {
1115
/**
1216
* The Google Maps API Key (see:
1317
* https://developers.google.com/maps/documentation/javascript/get-api-key)
1418
*/
15-
apiKey: string = null;
19+
apiKey?: string;
1620

1721
/**
1822
* The Google Maps client ID (for premium plans).
1923
* When you have a Google Maps APIs Premium Plan license, you must authenticate
2024
* your application with either an API key or a client ID.
2125
* The Google Maps API will fail to load if both a client ID and an API key are included.
2226
*/
23-
clientId: string = null;
27+
clientId?: string;
2428

2529
/**
2630
* The Google Maps channel name (for premium plans).
2731
* A channel parameter is an optional parameter that allows you to track usage under your client
2832
* ID by assigning a distinct channel to each of your applications.
2933
*/
30-
channel: string = null;
34+
channel?: string;
3135

3236
/**
3337
* Google Maps API version.
3438
*/
35-
apiVersion: string = '3';
39+
apiVersion?: string;
3640

3741
/**
3842
* Host and Path used for the `<script>` tag.
3943
*/
40-
hostAndPath: string = 'maps.googleapis.com/maps/api/js';
44+
hostAndPath?: string;
4145

4246
/**
4347
* Protocol used for the `<script>` tag.
4448
*/
45-
protocol: GoogleMapsScriptProtocol = GoogleMapsScriptProtocol.HTTPS;
49+
protocol?: GoogleMapsScriptProtocol;
4650

4751
/**
4852
* Defines which Google Maps libraries should get loaded.
4953
*/
50-
libraries: string[] = [];
54+
libraries?: string[];
5155

5256
/**
5357
* The default bias for the map behavior is US.
5458
* If you wish to alter your application to serve different map tiles or bias the
5559
* application, you can overwrite the default behavior (US) by defining a `region`.
5660
* See https://developers.google.com/maps/documentation/javascript/basics#Region
5761
*/
58-
region: string = null;
62+
region?: string;
5963

6064
/**
6165
* The Google Maps API uses the browser's preferred language when displaying
6266
* textual information. If you wish to overwrite this behavior and force the API
6367
* to use a given language, you can use this setting.
6468
* See https://developers.google.com/maps/documentation/javascript/basics#Language
6569
*/
70+
language?: string;
71+
}
72+
73+
/**
74+
* Configuration for {@link LazyMapsAPILoader}.
75+
* See {@link LazyMapsAPILoaderConfig} for instance attribute descriptions.
76+
*/
77+
export class LazyMapsAPILoaderConfig implements LazyMapsAPILoaderConfigLiteral {
78+
apiKey: string = null;
79+
clientId: string = null;
80+
channel: string = null;
81+
apiVersion: string = '3';
82+
hostAndPath: string = 'maps.googleapis.com/maps/api/js';
83+
protocol: GoogleMapsScriptProtocol = GoogleMapsScriptProtocol.HTTPS;
84+
libraries: string[] = [];
85+
region: string = null;
6686
language: string = null;
6787
}
6888

@@ -71,33 +91,36 @@ const DEFAULT_CONFIGURATION = new LazyMapsAPILoaderConfig();
7191
@Injectable()
7292
export class LazyMapsAPILoader extends MapsAPILoader {
7393
private _scriptLoadingPromise: Promise<void>;
94+
private _config: LazyMapsAPILoaderConfig;
95+
private _window: Window;
96+
private _document: Document;
7497

75-
constructor(@Optional() private _config: LazyMapsAPILoaderConfig) {
98+
constructor(@Optional() config: LazyMapsAPILoaderConfig, w: Window, d: Document) {
7699
super();
77-
if (this._config === null || this._config === undefined) {
78-
this._config = DEFAULT_CONFIGURATION;
79-
}
100+
this._config = config || DEFAULT_CONFIGURATION;
101+
this._window = w;
102+
this._document = d;
80103
}
81104

82105
load(): Promise<void> {
83106
if (this._scriptLoadingPromise) {
84107
return this._scriptLoadingPromise;
85108
}
86109

87-
const script = document.createElement('script');
110+
const script = this._document.createElement('script');
88111
script.type = 'text/javascript';
89112
script.async = true;
90113
script.defer = true;
91-
const callbackName: string = `angular2googlemaps${new Date().getMilliseconds() }`;
114+
const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`;
92115
script.src = this._getScriptSrc(callbackName);
93116

94117
this._scriptLoadingPromise = new Promise<void>((resolve: Function, reject: Function) => {
95-
(<any>window)[callbackName] = () => { resolve(); };
118+
(<any>this._window)[callbackName] = () => { resolve(); };
96119

97120
script.onerror = (error: Event) => { reject(error); };
98121
});
99122

100-
document.body.appendChild(script);
123+
this._document.body.appendChild(script);
101124
return this._scriptLoadingPromise;
102125
}
103126

@@ -119,40 +142,57 @@ export class LazyMapsAPILoader extends MapsAPILoader {
119142
}
120143

121144
const hostAndPath: string = this._config.hostAndPath || DEFAULT_CONFIGURATION.hostAndPath;
122-
const apiKey: string = this._config.apiKey || DEFAULT_CONFIGURATION.apiKey;
123-
const clientId: string = this._config.clientId || DEFAULT_CONFIGURATION.clientId;
124-
const channel: string = this._config.channel || DEFAULT_CONFIGURATION.channel;
125-
const libraries: string[] = this._config.libraries || DEFAULT_CONFIGURATION.libraries;
126-
const region: string = this._config.region || DEFAULT_CONFIGURATION.region;
127-
const language: string = this._config.language || DEFAULT_CONFIGURATION.language;
128-
const queryParams: {[key: string]: string} = {
145+
const queryParams: {[key: string]: string | Array<string>} = {
129146
v: this._config.apiVersion || DEFAULT_CONFIGURATION.apiVersion,
130-
callback: callbackName
147+
callback: callbackName,
148+
key: this._config.apiKey,
149+
client: this._config.clientId,
150+
channel: this._config.channel,
151+
libraries: this._config.libraries,
152+
region: this._config.region,
153+
language: this._config.language
131154
};
132-
if (apiKey) {
133-
queryParams['key'] = apiKey;
134-
}
135-
if (clientId) {
136-
queryParams['client'] = clientId;
137-
}
138-
if (channel) {
139-
queryParams['channel'] = channel;
140-
}
141-
if (libraries != null && libraries.length > 0) {
142-
queryParams['libraries'] = libraries.join(',');
143-
}
144-
if (region != null && region.length > 0) {
145-
queryParams['region'] = region;
146-
}
147-
if (language != null && language.length > 0) {
148-
queryParams['language'] = language;
149-
}
150-
const params: string = Object.keys(queryParams)
151-
.map((k: string, i: number) => {
152-
let param = (i === 0) ? '?' : '&';
153-
return param += `${k}=${queryParams[k]}`;
154-
})
155-
.join('');
156-
return `${protocol}//${hostAndPath}${params}`;
155+
const params: string =
156+
Object.keys(queryParams)
157+
.filter((k: string) => queryParams[k] != null)
158+
.filter((k: string) => {
159+
// remove empty arrays
160+
return !Array.isArray(queryParams[k]) ||
161+
(Array.isArray(queryParams[k]) && queryParams[k].length > 0);
162+
})
163+
.map((k: string) => {
164+
// join arrays as comma seperated strings
165+
let i = queryParams[k];
166+
if (Array.isArray(i)) {
167+
return {key: k, value: i.join(',')};
168+
}
169+
return {key: k, value: queryParams[k]};
170+
})
171+
.map((entry: {key: string, value: string}) => { return `${entry.key}=${entry.value}`; })
172+
.join('&');
173+
return `${protocol}//${hostAndPath}?${params}`;
157174
}
158175
}
176+
177+
/**
178+
* Creates a provider for a {@link LazyMapsAPILoaderConfig})
179+
*/
180+
export function provideLazyMapsAPILoaderConfig(confLiteral: LazyMapsAPILoaderConfigLiteral):
181+
Provider {
182+
return provide(LazyMapsAPILoaderConfig, {
183+
useFactory: () => {
184+
const config = new LazyMapsAPILoaderConfig();
185+
// todo(sebastian): deprecate LazyMapsAPILoader class
186+
config.apiKey = confLiteral.apiKey || DEFAULT_CONFIGURATION.apiKey;
187+
config.apiVersion = confLiteral.apiVersion || DEFAULT_CONFIGURATION.apiVersion;
188+
config.channel = confLiteral.channel || DEFAULT_CONFIGURATION.channel;
189+
config.clientId = confLiteral.clientId || DEFAULT_CONFIGURATION.clientId;
190+
config.hostAndPath = confLiteral.hostAndPath || DEFAULT_CONFIGURATION.hostAndPath;
191+
config.language = confLiteral.language || DEFAULT_CONFIGURATION.language;
192+
config.libraries = confLiteral.libraries || DEFAULT_CONFIGURATION.libraries;
193+
config.protocol = config.protocol || DEFAULT_CONFIGURATION.protocol;
194+
config.region = config.region || DEFAULT_CONFIGURATION.region;
195+
return config;
196+
}
197+
});
198+
}

src/core/utils/browser-globals.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {Provider, provide} from '@angular/core';
2+
3+
export const BROWSER_GLOBALS_PROVIDERS: Provider[] =
4+
[provide(Window, {useValue: window}), provide(Document, {useValue: document})];
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {provide} from '@angular/core';
2+
import {beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing';
3+
4+
import {LazyMapsAPILoader} from '../../../src/core/services/maps-api-loader/lazy-maps-api-loader';
5+
import {MapsAPILoader} from '../../../src/core/services/maps-api-loader/maps-api-loader';
6+
7+
export function main() {
8+
describe('Service: LazyMapsAPILoader', () => {
9+
beforeEachProviders(() => {
10+
return [
11+
provide(MapsAPILoader, {useClass: LazyMapsAPILoader}), provide(Window, {useValue: {}}),
12+
provide(
13+
Document, {useValue: jasmine.createSpyObj<Document>('Document', ['createElement'])})
14+
];
15+
});
16+
17+
it('should create the default script URL',
18+
inject([MapsAPILoader, Document, Window], (loader: LazyMapsAPILoader, doc: Document) => {
19+
interface Script {
20+
src?: string;
21+
async?: boolean;
22+
defer?: boolean;
23+
type?: string;
24+
}
25+
const scriptElem: Script = {};
26+
(<jasmine.Spy>doc.createElement).and.returnValue(scriptElem);
27+
doc.body = jasmine.createSpyObj('body', ['appendChild']);
28+
29+
loader.load();
30+
expect(doc.createElement).toHaveBeenCalled();
31+
expect(scriptElem.type).toEqual('text/javascript');
32+
expect(scriptElem.async).toEqual(true);
33+
expect(scriptElem.defer).toEqual(true);
34+
expect(scriptElem.src).toBeDefined();
35+
expect(scriptElem.src).toContain('https://maps.googleapis.com/maps/api/js');
36+
expect(scriptElem.src).toContain('v=3');
37+
expect(scriptElem.src).toContain('callback=angular2GoogleMapsLazyMapsAPILoader');
38+
expect(doc.body.appendChild).toHaveBeenCalledWith(scriptElem);
39+
}));
40+
});
41+
}

0 commit comments

Comments
 (0)