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

Posted

技术标签:

【中文标题】如何将第 3 方脚本从 Web 动态加载到 Angular2 组件中【英文标题】:How to load a 3rd party script from web dynamically into Angular2 component 【发布时间】:2016-06-09 15:12:42 【问题描述】:

我正在尝试从网络加载第 3 方脚本,而不是制作它的本地副本,并且能够在脚本加载后使用第 3 方脚本的全局变量和函数。

更新

这是我试图在纯 javascript 中实现的示例,其中单击 Visa Checkout 按钮打开 Visa Checkout 对话框: Plunker JS link 这是我需要帮助的 Angular2 版本: Plunker Angular2 link

问题:下面的组件没有从网络加载脚本

import Component from '@angular/core'

@Component(
  selector: 'custom',
  providers: [],
  template: `
    <div>
      <h2>name</h2>
      <img class="v-button" role="button"  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!!'
  

【问题讨论】:

【参考方案1】:

您可以使用此技术在 Angular 2/4 项目中按需动态加载 JS 脚本和库。

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'
];

创建 script.service.ts 以提供 ScriptService 作为可注入服务,该服务将处理脚本文件的加载。包含此代码:

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



在需要的地方注入 ScriptService 并加载如下脚本:

constructor(
    private scriptService: ScriptService
)  

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

【讨论】:

【参考方案2】:

我已修改 Rahul Kumar's answer 使其使用 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

【讨论】:

干杯...它可以加载...但是我将如何执行它?并模仿异步功能?【参考方案3】:

有两种方法可以实现。

    为要添加的第 3 方脚本引用类型定义文件。类型定义文件通常以.d.ts 结尾,基本上是脚本功能的接口。如果没有预定义的类型定义文件,您可以使用您需要的功能自己创建一个。 (我更喜欢这种方法,因为某些 IDE 会将方法签名作为智能感知的一部分提供给您) 在 TypeScript 类的顶部创建一个变量,该变量代表您正在使用的库,类型为 any

使用 AutoMapperTS 的示例:

类型定义:

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

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

(此示例中的引用可能因您的编辑器而异。此示例使用 Visual Studio。尝试将您要引用的文件拖到编辑器窗口中,看看您的 IDE 是否会为您创建引用。)

声明:

declare var automapper: any;

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

可以使用标准的&lt;script&gt; 标签导入来加载第 3 方 JS 文件。以上方法是针对TS编译器的,这样就不会因为未知变量异常而失败。

【讨论】:

感谢您的快速回复。我是 Angular 的新手,我不太明白你的回答。你能用 Plunker 的例子看看我的更新问题吗?非常感谢帮助! 我查看了您的 Plunker,但该站点现在已关闭。我看到的第一个问题是你不应该在组件的模板中包含script 标签,因为 Angular 会将它们剥离。第 3 部分脚本应加载到 index.html。 Plunker 恢复后,我会再看一遍。 虽然我想通过在 index.html 中包含脚本来了解它是如何工作的,但我更愿意在 my-component 时延迟加载脚本。在这种特殊情况下,我还需要在 my-component 中访问从 web 加载的脚本的全局变量。 我的研究表明您可以使用 System.import 延迟加载脚本文件。我在你的一个分支中尝试过这个,但没有成功。但是,稍后加载这个脚本而不是预先加载 index.html 所获得的性能提升将接近于 nil。 谢谢!正如你所提到的,我已经在 index.html 中添加了脚本并按照Include External JavaScript Libraries In An Angular 2 TypeScript Project 的说明进行操作

以上是关于如何将第 3 方脚本从 Web 动态加载到 Angular2 组件中的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Maven 将第 3 方 OSGi 捆绑包添加到部署包中?

如何在不重新加载页面的情况下从 FF Web 扩展内容脚本更改 3rd 方网站上的 Angular 应用程序路由/URL

如何重新加载经常崩溃的第 3 方 DLL

动态脚本加载到同一页面后如何调试非动态脚本

网络安全从入门到精通(第一章-2)快速自建web安全测试环境

动态加载js和css