9

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

更新

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

问题:下面的组件未从 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!!'
  }
}

4

3 回答 3

11

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

ScriptStorescript.store.ts中创建,它将包含本地或远程服务器上的脚本路径以及用于动态加载脚本的名称:

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.tsScriptService作为可注入服务提供,该服务将处理脚本文件的加载。包括此代码:

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));
}
于 2017-03-13T14:27:45.513 回答
3

我已经修改了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
}
于 2017-05-10T11:42:12.107 回答
2

有两种方法可以实现这一点。

  1. 引用您要添加的第 3 方脚本的类型定义文件。类型定义文件通常以脚本结尾.d.ts并且基本上是脚本功能的接口。如果没有预定义的类型定义文件,您可以使用您需要的功能自己创建一个。(我更喜欢这种方法,因为一些 IDE 会给你方法签名作为智能感知的一部分)
  2. 在 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);
    }
}

可以使用标准<script>标签导入加载第 3 方 JS 文件。上述方法适用于 TS 编译器,因此它不会因未知变量异常而失败。

于 2016-06-09T16:53:02.990 回答