下载 Angular 2 个组件和 运行 个脚本

Make Angular 2 component download and run a script

我正在尝试使用 MathJax 在我的 Angular 2 应用程序中显示方程式。我的应用程序只有一小部分实际上需要此功能,所以我不希望所有用户都必须下载所需的 3-400kb。

我最初的想法是 import "mathjax" 在我的组件中,这样只有在创建组件时才会加载库。但是,我了解到 MathJax 不能很好地与其他人一起使用,因为它使用自己的自定义模块加载器。

我当前的设置从我的 index.html 加载 script 但这意味着 每个人 都会在只有一小部分用户需要此组件时下载它

<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=AM_SVG"></script>

Angular 2 中是否有一种干净的方法让我的组件在 ngOnInit() 挂钩中下载脚本并在它进入 DOM 后执行回调?诸如(伪代码)之类的东西:

ngOnInit(){
    //download script, add to DOM      
    fetch('https://...MathJax.js').then(addToDom).then(this.onMathJaxLoaded);
}
onMathJaxLoaded(){
  // Run math renderer
  MathJax.Hub.Queue("Typeset",...);
}

我没有尝试过,但我认为这可能是可行的:

ngOnInit(){
    //download script, add to DOM      
    var script = document.createElement('script');
    document.body.appendChild(script)
    script.onload = this.onMathJaxLoaded.bind(this);
    script.src = 'https://...MathJax.js';
}
onMathJaxLoaded(){
  // Run math renderer
  MathJax.Hub.Queue("Typeset",...);
}

这是一个 fiddle,其中包含一个示例,说明它在普通 JavaScript 中如何工作; https://jsfiddle.net/5qu5h7bc/

基于@TryingToImprove 的解决方案,我决定在我的应用程序中添加一个 ScriptLoaderService,其中包含一个 load() 函数,该函数获取远程脚本并通知调用者(使用 RxJS Observable)完成后。

使用方法如下:

this.scriptLoaderService.load('https://...js').subscribe(
    ()=>console.log('script has loaded');
);

这是服务:

/**
 * A directory of scripts that have been or are being loaded
 */
private scripts = new Map<string,Observable<boolean>|boolean>();

/**
 * Downloads script; returns Observable that emits TRUE once "load" event fires
 */
public load(src:string):Observable<boolean>{        
    if(this.scripts.has(src)){
        // If script has already been fully loaded.
        if(this.scripts.get(src)===true) return Observable.of(true);

        // Else if download is already underway but load event hasn't fired yet
        else return this.scripts.get(src);
    }

    //Add a script tag to the DOM
    let script = document.createElement('script');
    document.body.appendChild(script);

    // upon subscription, listen for the load event. Once it arrives, emit TRUE
    let obs:Observable<boolean> = Observable.fromEvent(script,'load')
        // Map should hold TRUE once script is loaded
        .do(()=> this.scripts.set(src,true))
        .take(1).map(e=>true);

    // set the src attribute (will cause browser to download)
    script.src = src;

    //add observable to the scripts Map
    this.scripts.set(src,obs);

    return obs;
}

测试于 Firefox 51