diff --git a/docs/Config.md b/docs/Config.md index c07547e..bff8290 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -21,7 +21,8 @@ export class AppModule {} ## Providing a Google Maps API Key -When using GeoCharts, it might be necessary to provide a Maps API Key to access full functionality. +When using Geocharts or Map Charts, it might be necessary to provide a Maps API Key to avoid +the default request throttling of Googles servers. The Google Maps API Key can be configured using the `mapsApiKey` property in the config. @@ -31,3 +32,8 @@ You may want to specify a custom version of Google Charts, e.g. `'upcoming'`. More information on this can be found in the [official documentation](https://developers.google.com/chart/interactive/docs/basic_load_libs). The version can be configured using the `version` property in the config. + +## Sanitizing unsafe HTML + +Since version 47, Google Charts allows you to sanitize generated HTML and will automatically strip unsafe elements. +You can enable this behaviour by setting `safeMode` to true. diff --git a/libs/angular-google-charts/src/lib/google-charts.module.spec.ts b/libs/angular-google-charts/src/lib/google-charts.module.spec.ts index 9c29ec7..20beda9 100644 --- a/libs/angular-google-charts/src/lib/google-charts.module.spec.ts +++ b/libs/angular-google-charts/src/lib/google-charts.module.spec.ts @@ -1,7 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { GoogleChartsModule } from './google-charts.module'; -import { CHART_VERSION, MAPS_API_KEY } from './models/injection-tokens.model'; +import { GoogleChartsConfig } from './models/google-charts-config.model'; +import { GOOGLE_CHARTS_CONFIG } from './models/injection-tokens.model'; import { ScriptLoaderService } from './script-loader/script-loader.service'; describe('GoogleChartsModule', () => { @@ -23,14 +24,16 @@ describe('GoogleChartsModule', () => { describe('config via forRoot', () => { const mapsApiKey = 'myMapsApiKey'; const version = '13.5'; + const safeMode = false; it('should provide the given config values', () => { + const config: GoogleChartsConfig = { mapsApiKey, version, safeMode }; + TestBed.configureTestingModule({ - imports: [GoogleChartsModule.forRoot({ mapsApiKey, version })] + imports: [GoogleChartsModule.forRoot(config)] }); - expect(TestBed.inject(CHART_VERSION)).toBe(version); - expect(TestBed.inject(MAPS_API_KEY)).toBe(mapsApiKey); + expect(TestBed.inject(GOOGLE_CHARTS_CONFIG)).toEqual(config); }); it('should accept empty config', () => { @@ -38,8 +41,7 @@ describe('GoogleChartsModule', () => { imports: [GoogleChartsModule.forRoot()] }); - expect(TestBed.inject(CHART_VERSION)).toBeUndefined(); - expect(TestBed.inject(MAPS_API_KEY)).toBeUndefined(); + expect(TestBed.inject(GOOGLE_CHARTS_CONFIG)).toEqual({}); }); it('should accept a partial config', () => { @@ -47,8 +49,7 @@ describe('GoogleChartsModule', () => { imports: [GoogleChartsModule.forRoot({ mapsApiKey })] }); - expect(TestBed.inject(CHART_VERSION)).toBeUndefined(); - expect(TestBed.inject(MAPS_API_KEY)).toBe(mapsApiKey); + expect(TestBed.inject(GOOGLE_CHARTS_CONFIG)).toMatchObject({ mapsApiKey }); }); }); }); diff --git a/libs/angular-google-charts/src/lib/google-charts.module.ts b/libs/angular-google-charts/src/lib/google-charts.module.ts index 40a74c4..c4f0e25 100644 --- a/libs/angular-google-charts/src/lib/google-charts.module.ts +++ b/libs/angular-google-charts/src/lib/google-charts.module.ts @@ -1,8 +1,8 @@ import { ModuleWithProviders, NgModule } from '@angular/core'; import { GoogleChartComponent } from './google-chart/google-chart.component'; -import { Config } from './models/config.model'; -import { CHART_VERSION, MAPS_API_KEY } from './models/injection-tokens.model'; +import { GoogleChartsConfig } from './models/google-charts-config.model'; +import { GOOGLE_CHARTS_CONFIG } from './models/injection-tokens.model'; import { RawChartComponent } from './raw-chart/raw-chart.component'; @NgModule({ @@ -10,13 +10,10 @@ import { RawChartComponent } from './raw-chart/raw-chart.component'; exports: [GoogleChartComponent, RawChartComponent] }) export class GoogleChartsModule { - public static forRoot({ mapsApiKey, version }: Config = {}): ModuleWithProviders { + public static forRoot(config: GoogleChartsConfig = {}): ModuleWithProviders { return { ngModule: GoogleChartsModule, - providers: [ - { provide: MAPS_API_KEY, useValue: mapsApiKey }, - { provide: CHART_VERSION, useValue: version } - ] + providers: [{ provide: GOOGLE_CHARTS_CONFIG, useValue: config }] }; } } diff --git a/libs/angular-google-charts/src/lib/models/config.model.ts b/libs/angular-google-charts/src/lib/models/config.model.ts deleted file mode 100644 index dece1f3..0000000 --- a/libs/angular-google-charts/src/lib/models/config.model.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Config { - /** - * A Google Maps API key, used for GeoCharts. - * - * {@link https://developers.google.com/chart/interactive/docs/gallery/geochart GeoChart Documentation} - */ - mapsApiKey?: string; - /** - * Which version of Google Charts to use. - * Can be either a number specifying the concrete version (e.g. `45`, `45.1`) or a string (e.g. `'upcoming'`). - * - * {@link https://developers.google.com/chart/interactive/docs/basic_load_libs#basic-library-loading Offical Documentation} - */ - version?: string; -} diff --git a/libs/angular-google-charts/src/lib/models/google-charts-config.model.ts b/libs/angular-google-charts/src/lib/models/google-charts-config.model.ts new file mode 100644 index 0000000..7283789 --- /dev/null +++ b/libs/angular-google-charts/src/lib/models/google-charts-config.model.ts @@ -0,0 +1,39 @@ +export interface GoogleChartsConfig { + /** + * This setting lets you specify a key that you may use with Geochart and Map Chart. + * You may want to do this rather than use the default behavior which may result in + * occasional throttling of service for your users. + * + * Only available when using Google Charts 45 or higher. + * + * {@link https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings Parameter documentation } + * {@link https://developers.google.com/chart/interactive/docs/gallery/geochart GeoChart Documentation} + */ + mapsApiKey?: string; + + /** + * Which version of Google Charts to use. + * + * Please note that this library does only work with Google Charts 45 or higher. + * + * @description + * Can be either a number specifying a + * {@link https://developers.google.com/chart/interactive/docs/release_notes#current:-january-6,-2020 frozen version } of Google Charts + * or one of the special versions `current` and `upcoming`. + * + * Defaults to `current`. + * + * {@link https://developers.google.com/chart/interactive/docs/basic_load_libs#basic-library-loading Offical Documentation} + */ + version?: string; + + /** + * When set to true, all charts and tooltips that generate HTML from user-supplied data will sanitize it + * by stripping out unsafe elements and attributes. + * + * Only available when using GoogleCharts 47 or higher. + * + * {@link https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings Parameter documentation } + */ + safeMode?: boolean; +} diff --git a/libs/angular-google-charts/src/lib/models/injection-tokens.model.ts b/libs/angular-google-charts/src/lib/models/injection-tokens.model.ts index 792a9d1..13db340 100644 --- a/libs/angular-google-charts/src/lib/models/injection-tokens.model.ts +++ b/libs/angular-google-charts/src/lib/models/injection-tokens.model.ts @@ -1,4 +1,5 @@ import { InjectionToken } from '@angular/core'; -export const CHART_VERSION = new InjectionToken('CHART_VERSION'); -export const MAPS_API_KEY = new InjectionToken('MAPS_API_KEY'); +import { GoogleChartsConfig } from './google-charts-config.model'; + +export const GOOGLE_CHARTS_CONFIG = new InjectionToken('GOOGLE_CHARTS_CONFIG'); diff --git a/libs/angular-google-charts/src/lib/script-loader/script-loader.service.spec.ts b/libs/angular-google-charts/src/lib/script-loader/script-loader.service.spec.ts index efee8eb..76f837b 100644 --- a/libs/angular-google-charts/src/lib/script-loader/script-loader.service.spec.ts +++ b/libs/angular-google-charts/src/lib/script-loader/script-loader.service.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { CHART_VERSION, MAPS_API_KEY } from '../models/injection-tokens.model'; +import { GOOGLE_CHARTS_CONFIG } from '../models/injection-tokens.model'; import { ScriptLoaderService } from './script-loader.service'; @@ -104,10 +104,11 @@ describe('ScriptLoaderService', () => { service.loadChartPackages(chart).subscribe(); - expect(chartsMock.load).toHaveBeenCalledWith('46', { + expect(chartsMock.load).toHaveBeenCalledWith('current', { packages: [chart], language: 'en-US', - mapsApiKey: '' + mapsApiKey: '', + safeMode: false }); }); @@ -131,14 +132,14 @@ describe('ScriptLoaderService', () => { const version = 'current'; const mapsApiKey = 'mapsApiKey'; + const safeMode = true; const locale = 'de-DE'; TestBed.configureTestingModule({ providers: [ ScriptLoaderService, { provide: LOCALE_ID, useValue: locale }, - { provide: CHART_VERSION, useValue: version }, - { provide: MAPS_API_KEY, useValue: mapsApiKey } + { provide: GOOGLE_CHARTS_CONFIG, useValue: { version, mapsApiKey, safeMode } } ] }); service = TestBed.inject(ScriptLoaderService); @@ -150,7 +151,8 @@ describe('ScriptLoaderService', () => { expect(chartsMock.load).toHaveBeenCalledWith(version, { packages: [chart], language: locale, - mapsApiKey: mapsApiKey + mapsApiKey, + safeMode }); }); }); diff --git a/libs/angular-google-charts/src/lib/script-loader/script-loader.service.ts b/libs/angular-google-charts/src/lib/script-loader/script-loader.service.ts index 0ec624e..0484fda 100644 --- a/libs/angular-google-charts/src/lib/script-loader/script-loader.service.ts +++ b/libs/angular-google-charts/src/lib/script-loader/script-loader.service.ts @@ -2,18 +2,23 @@ import { Inject, Injectable, LOCALE_ID, Optional } from '@angular/core'; import { Observable, of, Subject } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import { CHART_VERSION, MAPS_API_KEY } from '../models/injection-tokens.model'; +import { GoogleChartsConfig } from '../models/google-charts-config.model'; +import { GOOGLE_CHARTS_CONFIG } from '../models/injection-tokens.model'; + +const DEFAULT_CONFIG: GoogleChartsConfig = { + mapsApiKey: '', + version: 'current', + safeMode: false +}; @Injectable({ providedIn: 'root' }) export class ScriptLoaderService { private readonly scriptSource = 'https://www.gstatic.com/charts/loader.js'; private readonly onLoadSubject = new Subject(); - constructor( - @Inject(LOCALE_ID) private localeId: string, - @Inject(MAPS_API_KEY) @Optional() private mapsApiKey?: string, - @Inject(CHART_VERSION) @Optional() private chartVersion?: string - ) {} + constructor(@Inject(LOCALE_ID) private localeId: string, @Inject(GOOGLE_CHARTS_CONFIG) @Optional() private config?: GoogleChartsConfig) { + this.config = { ...DEFAULT_CONFIG, ...(config || {}) }; + } /** * A stream that emits as soon as the google charts script is loaded (i.e. `google.charts` is available). @@ -59,10 +64,11 @@ export class ScriptLoaderService { const config = { packages, language: this.localeId, - mapsApiKey: this.mapsApiKey || '' + mapsApiKey: this.config.mapsApiKey, + safeMode: this.config.safeMode }; - google.charts.load(this.chartVersion || '46', config); + google.charts.load(this.config.version, config); google.charts.setOnLoadCallback(() => { observer.next(); observer.complete();