我有这个模块,它将外部库与额外的逻辑组件化,而不直接将<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项目中根据需要动态加载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));
嗨,你可以使用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);
}
onload函数可以用来在脚本加载后调用脚本函数,如果你必须在ngOnInit()中进行调用,这是非常有用的。
我已经修改了@rahul kumars的答案,所以它使用可观察的代替:
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
}
我希望能够:
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。