Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load a 3rd party script from web dynamically into Angular2 component

I am trying to load a 3rd party script from web, instead of making a local copy of it and be able to use the 3rd party script's global variables and functions after the script loads.

Update:

  • Here is an example of what I am trying to achieve in plain JavaScript where clicking on Visa Checkout button opens Visa Checkout dialog: Plunker JS link
  • Here is the Angular2 version of it where I need help: Plunker Angular2 link

Issue: Component below is not loading the script from web

import {Component} from '@angular/core'

@Component({
  selector: 'custom',
  providers: [],
  template: `
    <div>
      <h2>{{name}}</h2>
      <img class="v-button" role="button" alt="Visa Checkout" src="https://sandbox.secure.checkout.visa.com/wallet-services-web/xo/button.png">
      <script src="https://sandbox-assets.secure.checkout.visa.com/checkout-widget/resources/js/integration/v1/sdk.js">
</script>
    </div>
  `
})
export class CustomComponent {
  constructor() {
    this.name = 'Custom Component works!!'
  }
}
like image 508
Rishi Avatar asked Jun 09 '16 15:06

Rishi


3 Answers

I have modified Rahul Kumar's 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
}
like image 188
Joel Avatar answered Nov 09 '22 11:11

Joel


You can dynamically load JS scripts and libraries on demand in your Angular 2/4 project using this technique.

Create ScriptStore in script.store.ts that 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'}
];

Create script.service.ts to provide ScriptService as an injectable service that will handle the loading of script files. Include this code:

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 ScriptService wherever you need it and load scripts like this:

constructor(
    private scriptService: ScriptService
) { }

ngOnInit() {
    this.scriptService.load('filepicker', 'rangeSlider').then(data => {
        console.log('script loaded ', data);
    }).catch(error => console.log(error));
}
like image 39
Rahul Kumar Avatar answered Nov 09 '22 09:11

Rahul Kumar


There are two ways that this can be accomplished.

  1. Reference a type definition file for the the 3rd party script that you are adding. Type definition files typically end in .d.ts and are basically an interface for the functions of the script. If there is not a pre-defined type definition file you can create one your self with the functions that you need. (I prefer this method because some IDEs will give you the method signature as part of the intellisense)
  2. Create a variable at the top of the TypeScript class that represents the library that you are using with a type of any;

Example using AutoMapperTS:

Type Definition:

/// <reference path="../node_modules/automapper-ts/dist/automapper.d.ts" />

@Component({
    selector: "my-app",
})
export class AppComponent {
    constructor() {
        automapper.map("JSON", "myType", jsonObj);
    }
}

(The reference in this example may be different depending on your editor. This example is using Visual Studio. Try dragging the file you want to reference onto the editor window to see if your IDE will create the reference for you.)

Declaration:

declare var automapper: any;

@Component({
    selector: "my-app",
})
export class AppComponent {
    constructor() {
        automapper.map("JSON", "myType", jsonObj);
    }
}

The 3rd party JS file can be loaded using the standard <script> tag import. The above methods are for the TS compiler so that it won't fail with an unknown variable exception.

like image 29
Teddy Sterne Avatar answered Nov 09 '22 10:11

Teddy Sterne