In Angular applications sometimes it’s necessary to use external libraries like Google Maps, as in any other application as well. In this post I’ll show how to create a service for this job.
The basic idea is to use
document.createElement('script')
to add the script source and append it to the DOM head element. Two things need to be addressed specifically however:
1) Async functionality to await the script loaded event
2) Preventing multiple loading of the same script
The scripts service should haveĀ a ‘load’ function to load a single or multiple scripts.
A config array provides all the loadable scripts with a name and source attribute.
const myScripts = [ { name: 'googleMaps', src: 'https://maps.googleapis.com/maps...'} ]; @Injectable() export class ScriptService { private scripts: any = {}; constructor() { myScripts.forEach((script: any) => { this.scripts[script.name] = { loaded: false, src: script.src }; }); } // load a single or multiple scripts load(...scripts: string[]) { const promises: any[] = []; // push the returned promise of each loadScript call scripts.forEach((script) => promises.push(this.loadScript(script))); // return promise.all that resolves when all promises are resolved return Promise.all(promises); } // load the script loadScript(name: string) { return new Promise((resolve, reject) => { // resolve if already loaded if (this.scripts[name].loaded) { resolve({script: name, loaded: true, status: 'Already Loaded'}); } else { // load script const script = document.createElement('script'); script.type = 'text/javascript'; script.src = this.scripts[name].src; // cross browser handling of onLoaded event if (script.readyState) { // IE script.onreadystatechange = () => { if (script.readyState === 'loaded' || script.readyState === 'complete') { script.onreadystatechange = null; this.scripts[name].loaded = true; resolve({script: name, loaded: true, status: 'Loaded'}); } }; } else { // Others script.onload = () => { this.scripts[name].loaded = true; resolve({script: name, loaded: true, status: 'Loaded'}); }; } script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'}); // finally append the script tag in the DOM document.getElementsByTagName('head')[0].appendChild(script); } }); } }
Using it in a component would look something like this:
constructor(scriptService: ScriptService) { scriptService // one or more arguments .load('googleMaps') .then(data => { // script is loaded, use it this.googleMapsApiLoaded = true; }); }