简体   繁体   English

如何将第3方脚本从Web动态加载到Angular2组件

[英]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. 我正在尝试从Web加载第三方脚本,而不是对其进行本地复制,并且能够在脚本加载后使用第三方脚本的全局变量和函数。

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 这是我尝试在普通JavaScript中实现的示例,其中单击Visa Checkout按钮可打开Visa Checkout对话框: Plunker JS链接
  • Here is the Angular2 version of it where I need help: Plunker Angular2 link 这是我需要帮助的Angular2版本: Plunker Angular2链接

Issue: Component below is not loading the script from web 问题:以下组件无法从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!!' } } 

You can dynamically load JS scripts and libraries on demand in your Angular 2/4 project using this technique. 您可以使用此技术在Angular 2/4项目中按需动态加载JS脚本和库。

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: script.store.ts中创建ScriptStore ,它将包含本地或远程服务器上脚本的路径以及将用于动态加载脚本的名称

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. 创建script.service.ts以提供ScriptService作为可注入服务,该服务将处理脚本文件的加载。 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: 在需要的地方注入ScriptService并加载如下脚本:

constructor(
    private scriptService: ScriptService
) { }

ngOnInit() {
    this.scriptService.load('filepicker', 'rangeSlider').then(data => {
        console.log('script loaded ', data);
    }).catch(error => console.log(error));
}

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. 类型定义文件通常以.d.ts结尾,并且基本上是脚本功能的接口。 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) (我更喜欢这种方法,因为某些IDE会在智能感知中为您提供方法签名)
  2. Create a variable at the top of the TypeScript class that represents the library that you are using with a type of any ; 在TypeScript类的顶部创建一个变量,该变量代表您正在使用的类型为any

Example using AutoMapperTS: 使用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.) (此示例中的引用可能会有所不同,具体取决于您的编辑器。此示例使用的是Visual Studio。尝试将要引用的文件拖到编辑器窗口中,以查看IDE是否会为您创建引用。)

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. 可以使用标准<script>标签导入来加载第三方JS文件。 The above methods are for the TS compiler so that it won't fail with an unknown variable exception. 以上方法适用于TS编译器,因此不会因未知变量异常而失败。

I have modified Rahul Kumar's answer so that it uses Observables instead: 我修改了Rahul Kumar的答案,以便它改用Observables:

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM