How to load external scripts dynamically in Angular?
Asked Answered
C

23

229

I have this module which componentize the external library together with additional logic without adding the <script> tag directly into the index.html:

import 'http://external.com/path/file.js'
//import '../js/file.js'

@Component({
    selector: 'my-app',
    template: `
        <script src="http://iknow.com/this/does/not/work/either/file.js"></script>
        <div>Template</div>`
})
export class MyAppComponent {...}

I notice the import by ES6 spec is static and resolved during TypeScript transpiling rather than at runtime.

Anyway to make it configurable so the file.js will be loading either from CDN or local folder? How to tell Angular 2 to load a script dynamically?

Clean answered 28/12, 2015 at 8:14 Comment(2)
Possible duplicate of ES6 variable import name in node.js?Parabola
If you are working on the latest version of Angular, a more recent and up-to-date implementation can be found here htmlgoodies.com/javascript/…Cablegram
E
73

If you're using system.js, you can use System.import() at runtime:

export class MyAppComponent {
  constructor(){
    System.import('path/to/your/module').then(refToLoadedModule => {
      refToLoadedModule.someFunction();
    }
  );
}

If you're using webpack, you can take full advantage of its robust code splitting support with require.ensure :

export class MyAppComponent {
  constructor() {
     require.ensure(['path/to/your/module'], require => {
        let yourModule = require('path/to/your/module');
        yourModule.someFunction();
     }); 
  }
}
Eximious answered 28/12, 2015 at 8:20 Comment(12)
It seems like I need to use module loader specifically in the component. Well, that's fine since System is the future of loader, I think.Clean
@Clean bingo :-)Eximious
TypeScript Plugin for Sublime Text does not happy with System in the TypeScript code: Cannot find name 'System' but no error during transpiling and running. Both Angular2 and System script file already added into index.html. Anyway to import the System and make the plugin happy?Clean
but what if you are not using system.js!Departmentalize
It will work, but your console will be overcrowded with errors.Periwinkle
@MurhafSousli if you're using webpack, see edit. if you have a better solution, please post it!Eximious
@drewmoore I don't remember why I said so, require()/import should work fine as your answer now, +1Departmentalize
and if you don't use any of those? What's the old school's way?Cutter
From what I can tell these options don't work for scripts on the internet, just for local files. This doesn't seem to answer the original question as asked.Puffy
Hello. Should I import something in my component to be able to use System.import or require.ensure? I've tried to use them and both rise errors because my component doesn't recognize System nor require.ensure. Thanks in advance!Toomey
@Toomey Yes, not sure how this answer got accepted. see the answer below with renderer2Inverter
how to call the loaded function please?Inalienable
F
210

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));
Fala answered 13/3, 2017 at 14:30 Comment(21)
This works brilliantly. Shaves over 1MB off initial load size - I have a lot of libraries!Colombi
Looks like I spoke too soon. This solution does NOT work in iOS Safari.Colombi
after loading the script check whether it's appending to head of your domFala
Perhaps I'm not injecting correctly, but I'm getting this.script.load is not a functionCarollcarolle
Thanks. This works, but I am getting my script (a background animation) loaded in every other component. I want it to be loaded only on my first component (it is a login page). What can I do to achieve that? ThanksKneeland
I'm trying load dependent scripts for FlowPlayer with this method (flowplayer, flowplayer.hlsjs, flowplayer.hlsjs.light) but seems I'm having console errors about the first one, from second and third (flowplayer is not defined). Any ideas about that? Thanks!Gebler
Check if it getting appended to the head?Fala
It doesn't add in order.Emend
It doesn't scripts add in order because the code is using Promise.all(promises) for loading the scripts, you can modify this section of the code to resolve all promises sequentiallyFala
Are you injecting script service properlyFala
@mirypoko I believe that I went with a simpler solution here Hope this helpsCarollcarolle
@RahulKumar, I have added one Script in ScriptStore. I have implemented as you said. But when I call this.scriptService.load('scriptName'); then it gets called twice before script.onload() called for first call. I have checked that in private scripts: any = {}; variable of ScriptService, I have only one Script entry.Randeerandel
What appenned if you change route and component? do scripts remain in head tag? if the answer is YES, it would not be really correct. is there a way to remove scripts from the head if i am not in the specific component?Hypnogenesis
hi Rahul, thanks for the reply. In your opinion, how should I remove that script from the <head> tag? Where i have to do this check? I wouldn't know how to do itHypnogenesis
You can remove the script from <head> of the document on ngOnDestroy() event of the component.Fala
How do you call function that are in the js file? are they attached to the global window?Blocked
I modified your load function for sequential loading. Here is what I did. load(...scripts: string[]) {let promise = Promise.resolve(); scripts.forEach(script => { promise = promise.then(() => this.loadScript(script)).then(res => console.log(res));});} Thank you for your answer.Foretopsail
The question now is how do you call the functions loaded in the JS file?Boutwell
I need to call a script with some variables values, like an id and a token. How can I do that?Sg
how to call the loaded function ?Inalienable
Thank you! Works very well for appending js files for micro frontends.Hallway
E
73

If you're using system.js, you can use System.import() at runtime:

export class MyAppComponent {
  constructor(){
    System.import('path/to/your/module').then(refToLoadedModule => {
      refToLoadedModule.someFunction();
    }
  );
}

If you're using webpack, you can take full advantage of its robust code splitting support with require.ensure :

export class MyAppComponent {
  constructor() {
     require.ensure(['path/to/your/module'], require => {
        let yourModule = require('path/to/your/module');
        yourModule.someFunction();
     }); 
  }
}
Eximious answered 28/12, 2015 at 8:20 Comment(12)
It seems like I need to use module loader specifically in the component. Well, that's fine since System is the future of loader, I think.Clean
@Clean bingo :-)Eximious
TypeScript Plugin for Sublime Text does not happy with System in the TypeScript code: Cannot find name 'System' but no error during transpiling and running. Both Angular2 and System script file already added into index.html. Anyway to import the System and make the plugin happy?Clean
but what if you are not using system.js!Departmentalize
It will work, but your console will be overcrowded with errors.Periwinkle
@MurhafSousli if you're using webpack, see edit. if you have a better solution, please post it!Eximious
@drewmoore I don't remember why I said so, require()/import should work fine as your answer now, +1Departmentalize
and if you don't use any of those? What's the old school's way?Cutter
From what I can tell these options don't work for scripts on the internet, just for local files. This doesn't seem to answer the original question as asked.Puffy
Hello. Should I import something in my component to be able to use System.import or require.ensure? I've tried to use them and both rise errors because my component doesn't recognize System nor require.ensure. Thanks in advance!Toomey
@Toomey Yes, not sure how this answer got accepted. see the answer below with renderer2Inverter
how to call the loaded function please?Inalienable
L
68

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);
    }
}
Leifeste answered 20/7, 2016 at 5:57 Comment(10)
Thanx man Worked for me. You can now load it on page load also using ngonit method. So button click is not required.Hertel
I am using the same way.. but not worked for me . can u please help me out for the same ?? this.loadJs("javascriptfile path"); public loadjs(file) { let node = document.createElement('script'); node.src = file; node.type = 'text/javascript'; node.async = true; node.charset = 'utf-8'; document.getElementsByTagName('head')[0].appendChild(node); }Horatio
Are you trying to do it on click or on page load? Could you tell us more about the result or error?Leifeste
hello, I have tried to implement this and working fine but new Promise is not resolved. Can you tell me for Promise shall I have to import anything ?Delinda
Hi, I didn't import anything special when I use Promise.Leifeste
Hello @ng-darren, It worked well but do you have any idea how I can use PRODUCTION variable used in project's environment constant. Environment variable is exported as export const Environment = { PRODUCTION: false } Also, I am using this dynamically imported javascript only on some route's of project.Vinavinaceous
I used this technique, it worked at first, but when I need to load multiple js libs, in order, then its showing some problem. It shows all libs in the same order in browser console but the files that comes later in the order doesn't get the reference of its previous files. I used window.setTimeout( ... , 50) for my made scripts but for js libs its difficult !!!Patriarchate
I get this error -> GET iknow.com/this/does/not/work/either/file.js 403 (Forbidden)Dremadremann
that works for me but seems that scripts are loaded always async even set async=false.that cause some loading problemsFragrance
I tried wraping all of this in a if(!window.library). Somehow, the condition gets always failed even if the library is loaded onceBohun
A
36

Yet another option would be to utilize scriptjs package for that matter which

allows you to load script resources on-demand from any URL

Example

Install the package:

npm i scriptjs

and type definitions for scriptjs:

npm install --save @types/scriptjs

Then import $script.get() method:

import { get } from 'scriptjs';

and finally load script resource, in our case Google Maps library:

export class AppComponent implements OnInit {
  ngOnInit() {
    get("https://maps.googleapis.com/maps/api/js?key=", () => {
        //Google Maps library has been loaded...
    });
  }
}

Demo

Alchemist answered 27/10, 2018 at 22:31 Comment(5)
Great, but instead of npm install --save @types/scriptjs, it should be npm install --save-dev @types/scriptjs (or just npm i -D @types/scriptjs)Deiform
segment makes a bunch of function calls after the script is loaded. how would we achieve that with this example? i would need access to the analytics or script object I guess that it places globally?Paquito
also this broke my google maps component when I switched to this method. i'm assuming it's because the google maps component loaded before scriptjs was able to resolve it. probably because scriptjs runs in ngOnInit( which is after the google maps component has already loaded. This is most likely an issue with my implementation of the google maps component not being asynchronous but just wanted to throw that out there. Script loading and asynchronous javascript suck.Paquito
@Paquito did you solve it at the end?Slumberous
@Paquito were you able solve it at the end? Please respondTarango
T
33

I have modified @rahul kumars answer, so that it uses Observables instead:

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { Observer } from "rxjs/Observer";

@Injectable()
export class ScriptLoaderService {
    private scripts: ScriptModel[] = [];

    public load(script: ScriptModel): Observable<ScriptModel> {
        return new Observable<ScriptModel>((observer: Observer<ScriptModel>) => {
            var existingScript = this.scripts.find(s => s.name == script.name);

            // Complete if already loaded
            if (existingScript && existingScript.loaded) {
                observer.next(existingScript);
                observer.complete();
            }
            else {
                // Add the script
                this.scripts = [...this.scripts, script];

                // Load the script
                let scriptElement = document.createElement("script");
                scriptElement.type = "text/javascript";
                scriptElement.src = script.src;

                scriptElement.onload = () => {
                    script.loaded = true;
                    observer.next(script);
                    observer.complete();
                };

                scriptElement.onerror = (error: any) => {
                    observer.error("Couldn't load script " + script.src);
                };

                document.getElementsByTagName('body')[0].appendChild(scriptElement);
            }
        });
    }
}

export interface ScriptModel {
    name: string,
    src: string,
    loaded: boolean
}
Tactful answered 17/5, 2017 at 5:51 Comment(8)
You forget set the async to true.Farland
I have a question, when we append the script and navigate between routes, lets say I loaded script on home and browse to about and returned back to home, is there a way to reinit the current script ? Means I'd like to remove previously loaded one and load it again? Thank you very muchVegetable
@Vegetable Yes, if you'd want that it is possible. But you'd have to write some logic that removes the script tag. Have a look here: #14708689Tactful
Thanks Joel, but is there a way to somehow remove the functions initialized by the script ? Currently Im facing a problem in my angular4 project, because Im loading script which initialize jquery slider, so when the first route loads it displayed properly, but then when I navigate to other route and return back to my route, script is loaded twice and slider is not loading anymore :( Could you advice me on this pleaseVegetable
@Vegetable can't really help you, sorry. The code here should not load the same script twice. That is the purpose of saving the loaded scripts in the scripts arrayTactful
The thing is if I load it and navigate it stops working , I need to initialize my scripts written in jQuery againVegetable
There is an error in the line private scripts: {ScriptModel}[] = [];. It should be private scripts: ScriptModel[] = [];Razid
@Vegetable you should initialize the service only once. Use @Injectable({providedIn: 'root'}) and add the service only into the providers:[] array of app.module.tsElysian
M
29

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.

Midlothian answered 23/4, 2018 at 13:7 Comment(5)
I have to say, this looks simple but works well, very easy to implement :-)Strenuous
The problem with this type of script injection is that since Angular is a SPA, this scripts basically stays on the browser cache even after you remove the scripts tag from the DOM.Saurel
Awesome answer ! Saved my day, It works with Angular 6 & 7. Tested !Gunar
To send data to external js and and get data from external js , Call this function at app.component.ts and use declare var d3Sankey:any at any feature level module's component. It's working.Vlissingen
This worked for me with Angular 13. Thank you very much!Dolliedolloff
J
24

Hi you can use Renderer2 and elementRef with just a few lines of code:

constructor(private readonly elementRef: ElementRef,
          private renderer: Renderer2) {
}
ngOnInit() {
 const script = this.renderer.createElement('script');
 script.src = 'http://iknow.com/this/does/not/work/either/file.js';
 script.onload = () => {
   console.log('script loaded');
   initFile();
 };
 this.renderer.appendChild(this.elementRef.nativeElement, script);
}

the onload function can be used to call the script functions after the script is loaded, this is very useful if you have to do the calls in the ngOnInit()

Janycejanyte answered 17/1, 2020 at 14:36 Comment(0)
C
10

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

Consol answered 12/6, 2018 at 15:6 Comment(0)
J
8

I have a good way to dynamically load scripts! Now I use ng6, echarts4 (>700Kb ) ,ngx-echarts3 in my project. when I use them by ngx-echarts's docs, I need import echarts in angular.json : "scripts":["./node_modules/echarts/dist/echarts.min.js"] thus in the login module, page while loading scripts.js, this is big file! I don't want it.

So, I think angular loads each module as a file, I can insert a router resolver to preload js, then begin the module loading!

// PreloadScriptResolver.service.js

/**动态加载js的服务 */
@Injectable({
  providedIn: 'root'
})
export class PreloadScriptResolver implements Resolve<IPreloadScriptResult[]> {
  // Here import all dynamically js file
  private scripts: any = {
    echarts: { loaded: false, src: "assets/lib/echarts.min.js" }
  };
  constructor() { }
  load(...scripts: string[]) {
    const promises = scripts.map(script => this.loadScript(script));
    return Promise.all(promises);
  }
  loadScript(name: string): Promise<IPreloadScriptResult> {
    return new Promise((resolve, reject) => {
      if (this.scripts[name].loaded) {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      } else {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = this.scripts[name].src;
        script.onload = () => {
          this.scripts[name].loaded = true;
          resolve({ script: name, loaded: true, status: 'Loaded' });
        };
        script.onerror = (error: any) => reject({ script: name, loaded: false, status: 'Loaded Error:' + error.toString() });
        document.head.appendChild(script);
      }
    });
  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<IPreloadScriptResult[]> {
   return this.load(...route.routeConfig.data.preloadScripts);
  }
}

Then in the submodule-routing.module.ts ,import this PreloadScriptResolver:

const routes: Routes = [
  {
    path: "",
    component: DashboardComponent,
    canActivate: [AuthGuardService],
    canActivateChild: [AuthGuardService],
    resolve: {
      preloadScripts: PreloadScriptResolver
    },
    data: {
      preloadScripts: ["echarts"]  // important!
    },
    children: [.....]
}

This code works well, and its promises that: After js file loaded, then module begin load! this Resolver can use in many routers

Joyjoya answered 24/7, 2018 at 3:23 Comment(1)
Could you please share the code of IPreloadScriptResult interface?Confidence
M
5

An Angular universal solution; I needed to wait for a particular element to be on the page before loading a script to play a video.

import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from "@angular/common";

@Injectable({
  providedIn: 'root'
})
export class ScriptLoaderService {

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
  ) {
  }

  load(scriptUrl: string) {
    if (isPlatformBrowser(this.platformId)) {
      let node: any = document.createElement('script');
      node.src = scriptUrl;
      node.type = 'text/javascript';
      node.async = true;
      node.charset = 'utf-8';
      document.getElementsByTagName('head')[0].appendChild(node);
    }
  }
}
Masson answered 26/3, 2019 at 23:3 Comment(1)
This somehow feels dodgyObtuse
I
5

for those of you who would like to load styles dynamically too. (based on @Rahul Kumar brilliant answer)

script.store.ts

interface Scripts {
    name: string;
    src: string;
}

export const StyleStore: Scripts[] = [
    { name: 'fancybox-css', src: 'https://cdn.jsdelivr.net/gh/fancyapps/[email protected]/dist/jquery.fancybox.min.css' }
];

export const ScriptStore: Scripts[] = [
    { name: 'jquery', src: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js' },
    { name: 'other', src: '[other script source]'}
];

script-loader.service.ts

import { Injectable } from '@angular/core';
import { ScriptStore, StyleStore } from '../../stores/script.store';

@Injectable({
  providedIn: 'root'
})
export class ScriptLoaderService {

  private scripts: any = {};
  private styles: any = {};

  constructor() {
    ScriptStore.forEach((script: any) => {
      this.scripts[script.name] = {
        loaded: false,
        src: script.src
      };
    });

    StyleStore.forEach((script: any) => {
      this.styles[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);
  }

  loadStyles(...scripts: string[]) {
    var promises: any[] = [];
    scripts.forEach((script) => promises.push(this.loadStyle(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') as any;
        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);
      }
    });
  }

  loadStyle(name: string) {
    return new Promise((resolve, reject) => {
      //resolve if already loaded
      if (this.styles[name].loaded) {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      }
      else {
        //load style
        let style = document.createElement('link') as any;
        style.type = "text/css";
        style.rel = "stylesheet";
        style.href = this.styles[name].src;
        if (style.readyState) {  //IE
          style.onreadystatechange = () => {
            if (style.readyState === "loaded" || style.readyState === "complete") {
              style.onreadystatechange = null;
              this.styles[name].loaded = true;
              resolve({ style: name, loaded: true, status: 'Loaded' });
            }
          };
        } else {  //Others
          style.onload = () => {
            this.styles[name].loaded = true;
            resolve({ style: name, loaded: true, status: 'Loaded' });
          };
        }
        style.onerror = (error: any) => resolve({ style: name, loaded: false, status: 'Loaded' });
        document.getElementsByTagName('head')[0].appendChild(style);
      }
    });
  }

}

app.component.ts

constructor(private scriptLoaderService: ScriptLoaderService) {
  this.scriptLoaderService.loadStyles('fancybox-css').then(x => {
    this.scriptLoaderService.load('jquery', 'fancybox').then(data => {
    }).catch(error => console.log(error));
  });
}
Interlink answered 13/4, 2021 at 13:4 Comment(0)
S
4

You can dynamically load scripts in a component using the Renderer2 class from @angular/core.

import { Component, Renderer2 } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<p>Dynamic script loading example</p>',
})
export class AppComponent {
  constructor(private renderer: Renderer2) {}

  ngOnInit() {
    const script = this.renderer.createElement('script');
    this.renderer.setAttribute(script, 'src', 'https://example.com/script.js');
    this.renderer.appendChild(document.head, script);
  }
}
Shady answered 23/2, 2023 at 18:19 Comment(0)
V
3

In my case, I've loaded both the js and css visjs files using the above technique - which works great. I call the new function from ngOnInit()

Note: I could not get it to load by simply adding a <script> and <link> tag to the html template file.

loadVisJsScript() {
  console.log('Loading visjs js/css files...');
  let script = document.createElement('script');
  script.src = "../../assets/vis/vis.min.js";
  script.type = 'text/javascript';
  script.async = true;
  script.charset = 'utf-8';
  document.getElementsByTagName('head')[0].appendChild(script);    
	
  let link = document.createElement("link");
  link.type = "stylesheet";
  link.href = "../../assets/vis/vis.min.css";
  document.getElementsByTagName('head')[0].appendChild(link);    
}
Vengeful answered 22/3, 2018 at 22:17 Comment(2)
I want to load the CSS file from an external web source. Currently, i am facing the error saying "Not allowed to load local resource".. Please suggest.Tomb
I tried this way. Despite it's working well for Scripts files, it doesn't work for StylesheetsCyme
O
2

@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

Obtrude answered 11/11, 2017 at 10:38 Comment(1)
if i import multiple scripts foo,foo1,foo2 and i know only during the time of execution how to do the declaration dynamically?Iover
B
2

I had the same issue for the below link.I solved it in a very easy way.

https://www.gstatic.com/charts/loader.js

I needed to access the google variable in the below code. But it didn't work when I just put it in the angular class.

google.charts.load("current", {packages:['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
    var data = google.visualization.arrayToDataTable([
        ["Element", "Density", { role: "style" } ],
        ["Copper", 8.94, "dodgerblue"],
        ["Silver", 10.49, "dodgerblue"],
        ["Gold", 19.30, "dodgerblue"],
        ["Platinum", 21.45, "color: dodgerblue"]
    ]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1,
    { calc: "stringify",
        sourceColumn: 1,
        type: "string",
        role: "annotation" },
    2]);

var options = {
    title: "Density of Precious Metals, in g/cm^3",
    width: 600,
    height: 400,
    bar: {groupWidth: "50%"},
    legend: { position: "none" },
};
var chart = new google.visualization.ColumnChart(document.getElementById("columnchart_values"));
chart.draw(view, options);

}

I create a global variable on top of the ts class with the same name(google) and then that variable refer to the required one automatically.(because it is global scope) Then the problem is solved.

declare var google: any;
Baillieu answered 8/5, 2021 at 15:58 Comment(0)
M
2

I wanted to be able to:

  • Add a script when the app is being bootstrapped
  • Not do it from a component, because it doesn't feel like it's any component's responsibility
  • Not do it from a directive, because of the same reason as the component
  • Not do it from a service, because unless there's some kind of heavy logic related to an existing service, this doesn't belong IMO to a service
  • Avoid doing it in a module. A module could be fine but it's not as flexible as just using DI and since Angular 15 standalone components are stable so why bother with a module

That said, in order to do that even before the app is bootstrapped, it's a bit tricky. Because we don't have a renderer available at that stage AND we don't have access to an elementRef containing a nativeElement.

So here's my take on it:

export const YOUR_EXT_LIB_URL_TOKEN = new InjectionToken<string>('YOUR_EXT_LIB_URL_TOKEN');

export const YOUR_SETUP: Provider = {
  provide: APP_INITIALIZER,
  multi: true,
  useFactory: (
    doc: InjectionTokenType<typeof DOCUMENT>,
    rendererFactory: RendererFactory2,
    yourExternalLibToken: string,
  ) => {
    const renderer = rendererFactory.createRenderer(null, null);

    const script = renderer.createElement('script');
    script.type = 'text/javascript';
    script.src = yourExternalLibToken;
    renderer.appendChild(doc.body, script);

    return () => true;
  },
  deps: [DOCUMENT, RendererFactory2, YOUR_EXT_LIB_URL_TOKEN],
};

Then all you have to do is provide YOUR_EXT_LIB_URL_TOKEN and pass the YOUR_SETUP provider as well.

This way, everything is injected through DI and is super flexible. For example you could provide the YOUR_SETUP token in a shared library, and provide YOUR_EXT_LIB_URL_TOKEN in different apps that use the shared library.

Michaeu answered 29/11, 2022 at 16:31 Comment(0)
P
1

@d123546 I faced the same issue and got it working now using ngAfterContentInit (Lifecycle Hook) in the component like this :

import { Component, OnInit, AfterContentInit } from '@angular/core';
import { Router } from '@angular/router';
import { ScriptService } from '../../script.service';

@Component({
    selector: 'app-players-list',
    templateUrl: './players-list.component.html',
    styleUrls: ['./players-list.component.css'],
    providers: [ ScriptService ]
})
export class PlayersListComponent implements OnInit, AfterContentInit {

constructor(private router: Router, private script: ScriptService) {
}

ngOnInit() {
}

ngAfterContentInit() {
    this.script.load('filepicker', 'rangeSlider').then(data => {
    console.log('script loaded ', data);
    }).catch(error => console.log(error));
}
Pancratium answered 15/10, 2017 at 21:49 Comment(1)
Is there a specific reason to use ngAfterContentInit here? It looks like it should work fine from ngOnInit or even the constructorTibbetts
A
1

This solution worked for me :

1 ) create a new class named URLLoader

export class URLLoader {
  constructor() {

  }

  loadScripts() {

    const dynamicScripts = [
      'URL 1',
      'URL 2',
      'URL n'
    ];

    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('app-root')[0].appendChild(node);
    }
  }

}

2 ) extend the class URLLoader and invoke loadScripts method from the component class

export class AppComponent extends URLLoader implements OnInit {  

   constructor(){}

   ngOnInit() {
   super.loadScripts();
   }

}
Attaway answered 11/10, 2021 at 19:12 Comment(1)
How can we add function content inside script tag dynamicaly?Ddt
M
1

I find this solution much cleaner, first import HttpClientJsonpModule in your module and then do something like this

this.apiLoaded = this.httpClient.jsonp(environment.AnyApiUrl, 'callback')
  .pipe(
    map(() => true),
    catchError(() => of(false)),
  );

in your template:

<app-component *ngIf="apiLoaded | async"></app-component>

This solution is in official Angular Google Maps' docs here.

Meredith answered 13/11, 2021 at 7:0 Comment(1)
Beware: it will yield error if your API doesn't call callback, i.e. it's a static script instead of a valid JSONP.Blunder
B
1

Angular has the logic to prevent users directly interfere with the html output. So you have to let Angular to inject the tag by giving that direction in angular.json file.

First, you have to get the script file. There are two ways:

  1. Download the script file (eg. somelibrary.js)
  • place it on the assets folder
  • put the script's relative path, into the "scripts" section of the angular.json file:
"scripts": [
  "src/assets/somelibrary.js"
]
  1. Install the script with npm/yarn:
  • put the script's relative path, into the "scripts" section of the angular.json file:
"scripts": [
  "./node_modules/somelibrary/dist/somelibrary.min.js"
]
Bodine answered 26/1, 2022 at 17:24 Comment(0)
E
0

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();
});
Europeanize answered 18/7, 2018 at 7:29 Comment(0)
C
0
import { Injectable } from '@angular/core';
import * as $ from 'jquery';

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 };
            }
        });

        const 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) {
        debugger;
        loadOnce = loadOnce || false;

        scripts.forEach((script: string) => {
            if (!this._scripts[script]) {
                this._scripts[script] = { src: script, loaded: false };
            }
        });

        const 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) {
        debugger;
        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
                const 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 });
            }
        });
    }

    reloadOnSessionChange() {
        window.addEventListener('storage', function(data) {
            if (data['key'] === 'token' && data['oldValue'] == null && data['newValue']) {
                document.location.reload();
            }
        });
    }
}
Carlin answered 19/3, 2019 at 13:59 Comment(0)
U
-3

You can use Google Tag Manager to manage your external scripts without going into code. That s a perfect solution for non-tech users and tech users.

Underhung answered 27/3, 2021 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.