我有这个模块,它将外部库与其他逻辑组件化,而无需将标记直接添加到index.html中:
import 'http://external.com/path/file.js' //import '../js/file.js' @Component({ selector: 'my-app', template: `Template` }) export class MyAppComponent {...}
我注意到import
ES6规范是静态的,并且在TypeScript转换过程中而不是在运行时解析.
无论如何要使它可配置,以便file.js将从CDN或本地文件夹加载?如何告诉Angular 2动态加载脚本?
您可以使用以下技术在Angular项目中按需动态加载JS脚本和库.
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.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));
如果您正在使用system.js,则可以System.import()
在运行时使用:
export class MyAppComponent { constructor(){ System.import('path/to/your/module').then(refToLoadedModule => { refToLoadedModule.someFunction(); } ); }
如果您正在使用webpack,您可以充分利用其强大的代码拆分支持require.ensure
:
export class MyAppComponent { constructor() { require.ensure(['path/to/your/module'], require => { let yourModule = require('path/to/your/module'); yourModule.someFunction(); }); } }
这可能会奏效.此代码在单击按钮时动态地将标记附加到
head
html文件的标记.
const url = 'http://iknow.com/this/does/not/work/either/file.js'; export class MyAppComponent { loadAPI: Promise; 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); } }
我修改了@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 {
return new Observable((observer: Observer) => {
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
}
另一种选择是利用scriptjs
包装来解决这个问题
允许您从任何URL按需加载脚本资源
例
安装包:
npm i scriptjs
和类型定义scriptjs
:
npm install --save @types/scriptjs
然后导入$script.get()
方法:
import { get } from 'scriptjs';
最后加载脚本资源,在我们的案例中谷歌地图库:
export class AppComponent implements OnInit { ngOnInit() { get("https://maps.googleapis.com/maps/api/js?key=", () => { //Google Maps library has been loaded... }); } }
演示
您可以在文件中动态加载多个脚本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); } }
并在构造函数中调用此方法,
constructor() { this.loadScripts(); }
注意:要动态加载更多脚本,请将它们添加到dynamicScripts
数组中.