我有这个模块,它将外部库与额外的逻辑组件化,而不直接将<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动态加载脚本?


当前回答

Angular有防止用户直接干扰html输出的逻辑。所以你必须让Angular通过在Angular中给出那个方向来注入标签。json文件。

首先,您必须获取脚本文件。有两种方法:

下载脚本文件(例如。somelibrary.js)

把它放在资产文件夹中 把脚本的相对路径,放到angular的“scripts”部分。json文件:

"scripts": [
  "src/assets/somelibrary.js"
]

使用npm/yarn安装脚本:

把脚本的相对路径,放到angular的“scripts”部分。json文件:

"scripts": [
  "./node_modules/somelibrary/dist/somelibrary.min.js"
]

其他回答

你可以使用以下技巧 在你的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));

我发现这个解决方案更干净,首先在你的模块中导入HttpClientJsonpModule,然后做这样的事情

this.apiLoaded = this.httpClient.jsonp(environment.AnyApiUrl, 'callback')
  .pipe(
    map(() => true),
    catchError(() => of(false)),
  );

在模板中:

<app-component *ngIf="apiLoaded | async"></app-component>

这个解决方案在Angular谷歌Maps的官方文档中。

我希望能够:

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有防止用户直接干扰html输出的逻辑。所以你必须让Angular通过在Angular中给出那个方向来注入标签。json文件。

首先,您必须获取脚本文件。有两种方法:

下载脚本文件(例如。somelibrary.js)

把它放在资产文件夹中 把脚本的相对路径,放到angular的“scripts”部分。json文件:

"scripts": [
  "src/assets/somelibrary.js"
]

使用npm/yarn安装脚本:

把脚本的相对路径,放到angular的“scripts”部分。json文件:

"scripts": [
  "./node_modules/somelibrary/dist/somelibrary.min.js"
]

这可能有用。这段代码动态地将<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);
    }
}