I have this module which componentize the external library together with additional logic without adding the tag directly into the index.html:
You can load multiple scripts dynamically like this in your component.ts
file:
loadScripts() {
const dynamicScripts = [
'https://platform.twitter.com/widgets.js',
'../../../assets/js/dummyjs.min.js'
];
for (let i = 0; i < dynamicScripts.length; i++) {
const node = document.createElement('script');
node.src = dynamicScripts[i];
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
and call this method inside the constructor,
constructor() {
this.loadScripts();
}
Note : For more scripts to be loaded dynamically, add them to dynamicScripts
array.
a sample can be
script-loader.service.ts file
import {Injectable} from '@angular/core';
import * as $ from 'jquery';
declare let document: any;
interface Script {
src: string;
loaded: boolean;
}
@Injectable()
export class ScriptLoaderService {
public _scripts: Script[] = [];
/**
* @deprecated
* @param tag
* @param {string} scripts
* @returns {Promise<any[]>}
*/
load(tag, ...scripts: string[]) {
scripts.forEach((src: string) => {
if (!this._scripts[src]) {
this._scripts[src] = {src: src, loaded: false};
}
});
let promises: any[] = [];
scripts.forEach((src) => promises.push(this.loadScript(tag, src)));
return Promise.all(promises);
}
/**
* Lazy load list of scripts
* @param tag
* @param scripts
* @param loadOnce
* @returns {Promise<any[]>}
*/
loadScripts(tag, scripts, loadOnce?: boolean) {
loadOnce = loadOnce || false;
scripts.forEach((script: string) => {
if (!this._scripts[script]) {
this._scripts[script] = {src: script, loaded: false};
}
});
let promises: any[] = [];
scripts.forEach(
(script) => promises.push(this.loadScript(tag, script, loadOnce)));
return Promise.all(promises);
}
/**
* Lazy load a single script
* @param tag
* @param {string} src
* @param loadOnce
* @returns {Promise<any>}
*/
loadScript(tag, src: string, loadOnce?: boolean) {
loadOnce = loadOnce || false;
if (!this._scripts[src]) {
this._scripts[src] = {src: src, loaded: false};
}
return new Promise((resolve, reject) => {
// resolve if already loaded
if (this._scripts[src].loaded && loadOnce) {
resolve({src: src, loaded: true});
}
else {
// load script tag
let scriptTag = $('<script/>').
attr('type', 'text/javascript').
attr('src', this._scripts[src].src);
$(tag).append(scriptTag);
this._scripts[src] = {src: src, loaded: true};
resolve({src: src, loaded: true});
}
});
}
}
and usage
first inject
constructor(
private _script: ScriptLoaderService) {
}
then
ngAfterViewInit() {
this._script.loadScripts('app-wizard-wizard-3',
['assets/demo/default/custom/crud/wizard/wizard.js']);
}
or
this._script.loadScripts('body', [
'assets/vendors/base/vendors.bundle.js',
'assets/demo/default/base/scripts.bundle.js'], true).then(() => {
Helpers.setLoading(false);
this.handleFormSwitch();
this.handleSignInFormSubmit();
this.handleSignUpFormSubmit();
this.handleForgetPasswordFormSubmit();
});
You can use following technique to dynamically load JS scripts and libraries on demand in your Angular project.
script.store.ts will contain the path of the script either locally or on a remote server and a name that will be used to load the script dynamically
interface Scripts {
name: string;
src: string;
}
export const ScriptStore: Scripts[] = [
{name: 'filepicker', src: 'https://api.filestackapi.com/filestack.js'},
{name: 'rangeSlider', src: '../../../assets/js/ion.rangeSlider.min.js'}
];
script.service.ts is an injectable service that will handle the loading of script, copy script.service.ts
as it is
import {Injectable} from "@angular/core";
import {ScriptStore} from "./script.store";
declare var document: any;
@Injectable()
export class ScriptService {
private scripts: any = {};
constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src
};
});
}
load(...scripts: string[]) {
var promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}
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
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
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'});
document.getElementsByTagName('head')[0].appendChild(script);
}
});
}
}
Inject this ScriptService
wherever you need it and load js libs like this
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
@rahul-kumar 's solution works good for me, but i wanted to call my javascript function in my typescript
foo.myFunctions() // works in browser console, but foo can't be used in typescript file
I fixed it by declaring it in my typescript :
import { Component } from '@angular/core';
import { ScriptService } from './script.service';
declare var foo;
And now, i can call foo anywhere in my typecript file
I have done this code snippet with the new renderer api
constructor(private renderer: Renderer2){}
addJsToElement(src: string): HTMLScriptElement {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
this.renderer.appendChild(document.body, script);
return script;
}
And then call it like this
this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
console.log('SkyScanner Tag loaded');
}
StackBlitz
This might work. This Code dynamically appends the <script>
tag to the head
of the html file on button clicked.
const url = 'http://iknow.com/this/does/not/work/either/file.js';
export class MyAppComponent {
loadAPI: Promise<any>;
public buttonClicked() {
this.loadAPI = new Promise((resolve) => {
console.log('resolving promise...');
this.loadScript();
});
}
public loadScript() {
console.log('preparing to load...')
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
node.async = true;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}