在Angular 2中dynamic加载外部js脚本
我有这个模块将外部库与其他逻辑组件在一起,而不需要将<script>
标签直接添加到index.html中:
import 'path/file.html' //import '../js/file.js' @Component({ selector: 'my-app', template: ` <script src="this/does/not/work/either/file.html"></script> <div>Template</div>` }) export class MyAppComponent {...}
我注意到ES6规范的import
是静态的,并且在TypeScript转译期间而不是在运行时被解决。
无论如何,使其可configuration,所以file.js将从CDN或本地文件夹加载? 如何让Angular 2dynamic加载脚本?
如果你使用的是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(); }); } }
您可以使用以下技术在您的Angular 2/4项目中按需dynamic加载JS脚本和库。
script.store.ts将包含本地或远程服务器上的脚本path以及将用于dynamic加载脚本的名称
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));
这可能工作。 这个代码dynamic地将<script>
标签添加到button被点击的html文件的head
。
const url = 'this/does/not/work/either/file.html'; 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); } }
我已经修改了@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 }
@ d123546我遇到了同样的问题,现在使用组件中的ngAfterContentInit(生命周期钩子)工作,像这样:
import { Component, OnInit, AfterContentInit } from '@angular/core'; import { Router } from '@angular/router'; import { ScriptService } from '../../script.service'; @Component({ selector: 'app-players-list', templateUrl: './players-list.component.html', styleUrls: ['./players-list.component.css'], providers: [ ScriptService ] }) export class PlayersListComponent implements OnInit, AfterContentInit { constructor(private router: Router, private script: ScriptService) { } ngOnInit() { } ngAfterContentInit() { this.script.load('filepicker', 'rangeSlider').then(data => { console.log('script loaded ', data); }).catch(error => console.log(error)); }
@ rahul-kumar的解决scheme对我很好,但我想在我的打字稿中调用我的javascriptfunction
foo.myFunctions() // works in browser console, but foo can't be used in typescript file
我通过在我的脚本中声明来修复它:
import { Component } from '@angular/core'; import { ScriptService } from './script.service'; declare var foo;
而现在,我可以在我的打字稿中的任何地方打电话给foo