我有这个模块,它将外部库与额外的逻辑组件化,而不直接将<script>标记添加到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 {...}

我注意到ES6规范的导入是静态的,并且是在TypeScript编译期间解析的,而不是在运行时。

总之,让它变得可配置,这样file。js就会从CDN或本地文件夹加载? 如何告诉Angular 2动态加载脚本?


当前回答

我希望能够:

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

也就是说,为了在应用程序引导之前做到这一点,这有点棘手。因为我们在那个阶段没有可用的渲染器,并且我们不能访问包含nativeElement的elementRef。

下面是我的看法:

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],
};

然后,您所要做的就是提供YOUR_EXT_LIB_URL_TOKEN并传递YOUR_SETUP提供程序。

这样,所有东西都是通过DI注入的,非常灵活。例如,您可以在共享库中提供YOUR_SETUP令牌,并在使用共享库的不同应用程序中提供YOUR_EXT_LIB_URL_TOKEN。

其他回答

你可以使用以下技巧 在你的Angular项目中根据需要动态加载JS脚本和库。

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.ts是一个可注入的服务,它将处理脚本的加载,原样复制script.service.ts

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注入到任何你需要它的地方,并像这样加载js库

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

对于下面的链接,我也有同样的问题。我用一种很简单的方法解决了它。

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

我需要访问下面代码中的谷歌变量。但当我把它放到angular类中时,它就不起作用了。

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

}

我在ts类的顶部创建了一个具有相同名称的全局变量(谷歌),然后该变量自动引用所需的变量。(因为它是全局作用域)那么问题就解决了。

declare var google: any;

这可能有用。这段代码动态地将<script>标记附加到单击按钮时的html文件头部。

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

您可以像这样在组件中动态加载多个脚本。ts文件:

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

并在构造函数中调用这个方法,

constructor() {
    this.loadScripts();
}

注意:如果需要动态加载更多脚本,请将它们添加到dynamicScripts数组中。

我已经用新的渲染器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;
  }

然后像这样叫它

this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
        console.log('SkyScanner Tag loaded');
} 

堆栈闪电战