关于javascript:使Angular 2组件下载并运行脚本

Make Angular 2 component download and run a script

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

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

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

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

Angular 2中,有没有一种干净的方法让我的组件在ngOnInit()钩子中下载一个脚本,并在dom中执行回调?例如(伪代码):

1
2
3
4
5
6
7
8
ngOnInit(){
    //download script, add to DOM      
    fetch('https://...MathJax.js').then(addToDom).then(this.onMathJaxLoaded);
}
onMathJaxLoaded(){
  // Run math renderer
  MathJax.Hub.Queue("Typeset",...);
}


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

1
2
3
4
5
6
7
8
9
10
11
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)。

以下是如何使用它:

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

服务内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * 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上测试