Javascript 如何在 Angular 中动态加载外部脚本?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34489916/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to load external scripts dynamically in Angular?
提问by CallMeLaNN
I have this module which componentize the external library together with additional logic without adding the <script>
tag directly into the index.html:
我有这个模块将外部库与附加逻辑一起组件化,而无需将<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 {...}
I notice the import
by ES6 spec is static and resolved during TypeScript transpiling rather than at runtime.
我注意到import
ES6 规范是静态的,并且在 TypeScript 转译期间而不是在运行时解析。
Anyway to make it configurable so the file.js will be loading either from CDN or local folder? How to tell Angular 2 to load a script dynamically?
无论如何要使其可配置,以便 file.js 将从 CDN 或本地文件夹加载?如何告诉 Angular 2 动态加载脚本?
采纳答案by drew moore
If you're using system.js, you can use System.import()
at runtime:
如果你使用 system.js,你可以System.import()
在运行时使用:
export class MyAppComponent {
constructor(){
System.import('path/to/your/module').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}
If you're using webpack, you can take full advantage of its robust code splitting support with require.ensure
:
如果您正在使用 webpack,您可以通过以下方式充分利用其强大的代码拆分支持require.ensure
:
export class MyAppComponent {
constructor() {
require.ensure(['path/to/your/module'], require => {
let yourModule = require('path/to/your/module');
yourModule.someFunction();
});
}
}
回答by Rahul Kumar
You can use following technique to dynamically load JS scripts and libraries on demand in your Angular project.
您可以使用以下技术在 Angular 项目中按需动态加载 JS 脚本和库。
script.store.tswill contain the pathof the script either locally or on a remote server and a namethat will be used to load the script dynamically
script.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.tsis an injectable service that will handle the loading of script, copy script.service.ts
as it is
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);
}
});
}
}
Inject this ScriptService
wherever you need it and load js libs like this
ScriptService
在任何需要的地方注入它并像这样加载js库
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
回答by ng-darren
This might work. This Code dynamically appends the <script>
tag to the head
of the html file on button clicked.
这可能会奏效。此代码在单击按钮时动态地将<script>
标记附加到head
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);
}
}
回答by Joel
I have modified @rahul kumars answer, so that it uses Observables instead:
我修改了@rahul kumars 的答案,以便它使用 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
}
回答by Vadim Gremyachev
Yet another option would be to utilize scriptjs
packagefor that matter which
另一种选择是使用scriptjs
包来解决这个问题
allows you to load script resources on-demand from any URL
允许您从任何 URL 按需加载脚本资源
Example
例子
Install the package:
安装软件包:
npm i scriptjs
and type definitions for scriptjs
:
npm install --save @types/scriptjs
Then import $script.get()
method:
然后导入$script.get()
方法:
import { get } from 'scriptjs';
and finally load script resource, in our case Google Maps library:
最后加载脚本资源,在我们的例子中是谷歌地图库:
export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}
回答by Aswin Sanakan
You can load multiple scripts dynamicallylike this in your component.ts
file:
您可以像这样在文件中动态加载多个脚本component.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);
}
}
and call this method inside the constructor,
并在构造函数中调用此方法,
constructor() {
this.loadScripts();
}
Note : For more scripts to be loaded dynamically, add them to dynamicScripts
array.
注意:要动态加载更多脚本,请将它们添加到dynamicScripts
数组中。
回答by Eduardo Vargas
I have done this code snippet with the new renderer api
我已经用新的渲染器 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;
}
And then call it like this
然后这样称呼
this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
console.log('SkyScanner Tag loaded');
}
回答by u10125338
I have a good way to dynamically load scripts! Now I use ng6, echarts4 (>700Kb ) ,ngx-echarts3 in my project. when I use them by ngx-echarts's docs, I need import echarts in angular.json : "scripts":["./node_modules/echarts/dist/echarts.min.js"] thus in the login module, page while loading scripts.js, this is big file! I don't want it.
我有一个动态加载脚本的好方法!现在我在我的项目中使用 ng6, echarts4 (>700Kb ) ,ngx-echarts3。当我通过 ngx-echarts 的文档使用它们时,我需要在 angular.json 中导入 echarts : "scripts":["./node_modules/echarts/dist/echarts.min.js"] 因此在登录模块中,加载脚本时的页面.js,这是一个大文件!我不要。
So, I think angular loads each module as a file, I can insert a router resolver to preload js, then begin the module loading!
所以,我认为angular将每个模块作为一个文件加载,我可以插入一个路由器解析器来预加载js,然后开始加载模块!
// PreloadScriptResolver.service.js
// PreloadScriptResolver.service.js
/**动态加载js的服务 */
@Injectable({
providedIn: 'root'
})
export class PreloadScriptResolver implements Resolve<IPreloadScriptResult[]> {
// Here import all dynamically js file
private scripts: any = {
echarts: { loaded: false, src: "assets/lib/echarts.min.js" }
};
constructor() { }
load(...scripts: string[]) {
const promises = scripts.map(script => this.loadScript(script));
return Promise.all(promises);
}
loadScript(name: string): Promise<IPreloadScriptResult> {
return new Promise((resolve, reject) => {
if (this.scripts[name].loaded) {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
} else {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
script.onload = () => {
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
};
script.onerror = (error: any) => reject({ script: name, loaded: false, status: 'Loaded Error:' + error.toString() });
document.head.appendChild(script);
}
});
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<IPreloadScriptResult[]> {
return this.load(...route.routeConfig.data.preloadScripts);
}
}
Then in the submodule-routing.module.ts ,import this PreloadScriptResolver:
然后在 submodule-routing.module.ts 中,导入这个 PreloadScriptResolver:
const routes: Routes = [
{
path: "",
component: DashboardComponent,
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
resolve: {
preloadScripts: PreloadScriptResolver
},
data: {
preloadScripts: ["echarts"] // important!
},
children: [.....]
}
This code works well, and its promises that: After js file loaded, then module begin load! this Resolver can use in many routers
这段代码运行良好,它的承诺是:加载 js 文件后,然后模块开始加载!这个解析器可以在许多路由器中使用
回答by robert king
An Angular universal solution; I needed to wait for a particular element to be on the page before loading a script to play a video.
Angular 通用解决方案;在加载脚本以播放视频之前,我需要等待特定元素出现在页面上。
import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from "@angular/common";
@Injectable({
providedIn: 'root'
})
export class ScriptLoaderService {
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
) {
}
load(scriptUrl: string) {
if (isPlatformBrowser(this.platformId)) {
let node: any = document.createElement('script');
node.src = scriptUrl;
node.type = 'text/javascript';
node.async = true;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
}
回答by Hetdev
Hi you can use Renderer2 and elementRef with just a few lines of code:
嗨,您只需几行代码即可使用 Renderer2 和 elementRef:
constructor(private readonly elementRef: ElementRef,
private renderer: Renderer2) {
}
ngOnInit() {
const script = this.renderer.createElement('script');
script.src = 'http://iknow.com/this/does/not/work/either/file.js';
script.onload = () => {
console.log('script loaded');
initFile();
};
this.renderer.appendChild(this.elementRef.nativeElement, script);
}
the onload
function can be used to call the script functions after the script is loaded, this is very useful if you have to do the calls in the ngOnInit()
该onload
函数可用于在脚本加载后调用脚本函数,如果您必须在 ngOnInit() 中进行调用,这将非常有用