ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Angular 中动态加载外部脚本?

我有这个模块,它将外部库与其他逻辑一起组件化,而无需将 <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 规范中的 import 是静态的,并且在 TypeScript 转译期间而不是在运行时解决。

无论如何要使其可配置,以便 file.js 将从 CDN 或本地文件夹加载?如何告诉 Angular 2 动态加载脚本?


i
isherwood

您可以使用以下技术在 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));

这非常有效。将初始加载大小减少 1MB 以上 - 我有很多库!
看来我说得太早了。此解决方案不适用于 iOS Safari。
加载脚本后检查它是否附加到你的 dom 的头部
如何调用 js 文件中的函数?它们是否附加到全局窗口?
我修改了您的加载功能以进行顺序加载。这就是我所做的。 load(...scripts: string[]) {let promise = Promise.resolve(); scripts.forEach(script => { promise = promise.then(() => this.loadScript(script)).then(res => console.log(res));});} 感谢您的回答。
B
Bruno Bruzzano

如果您使用的是 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();
     }); 
  }
}

似乎我需要在组件中专门使用模块加载器。好吧,这很好,因为我认为 System 是加载器的未来。
TypeScript Plugin for Sublime Text 对 TypeScript 代码中的 System 不满意:Cannot find name 'System' 但在转译和运行期间没有错误。 Angular2System 脚本文件都已添加到 index.html。无论如何要import System 并使插件满意?
但是如果你不使用 system.js 怎么办!
@drewmoore 我不记得我为什么这么说,require()/import 现在应该可以作为您的答案了,+1
据我所知,这些选项不适用于 Internet 上的脚本,仅适用于本地文件。这似乎没有回答最初的问题。
R
Rahul Kumar

这可能会奏效。此代码在单击按钮时将 <script> 标记动态附加到 html 文件的 head

const url = 'http://iknow.com/this/does/not/work/either/file.js';

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);
    }
}

谢谢人为我工作。您现在也可以使用 ngoni 方法在页面加载时加载它。因此不需要单击按钮。
我正在使用相同的方式..但对我不起作用。你能帮我解决同样的问题吗? this.loadJs("javascript 文件路径"); public loadjs(file) { let node = document.createElement('script'); node.src = 文件; node.type = '文本/javascript'; node.async = true; node.charset = 'utf-8'; document.getElementsByTagName('head')[0].appendChild(node); }
您是在点击还是在页面加载时尝试这样做?你能告诉我们更多关于结果或错误的信息吗?
您好,我已尝试实现此功能并且工作正常,但 new Promise 未解决。你能告诉我Promise我需要导入什么吗?
嗨,我在使用 Promise 时没有导入任何特殊的东西。
V
Vadim Gremyachev

另一种选择是使用 scriptjs package

允许您从任何 URL 按需加载脚本资源

例子

安装包:

npm i scriptjs

type definitions for 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...
    });
  }
}

Demo


很好,但不是 npm install --save @types/scriptjs,它应该是 npm install --save-dev @types/scriptjs(或者只是 npm i -D @types/scriptjs)
脚本加载后,段会进行一堆函数调用。我们将如何通过这个例子实现这一目标?我需要访问 analyticsscript 对象,我猜它是全局放置的?
当我切换到这种方法时,这也破坏了我的谷歌地图组件。我假设这是因为在 scriptjs 之前加载的谷歌地图组件能够解决它。可能是因为 scriptjs 在 google maps 组件已经加载之后在 ngOnInit( 中运行。这很可能是我的谷歌地图组件实现不是异步的,只是想把它扔出去。脚本加载和异步 javascript 很糟糕。
J
Joel

我已经修改了@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
}

您忘记将 async 设置为 true
我有一个问题,当我们附加脚本并在路由之间导航时,假设我在 home 加载脚本并浏览到 about 并返回 home,有没有办法重新初始化当前脚本?意味着我想删除以前加载的一个并再次加载它?非常感谢
@d123546 是的,如果您愿意,这是可能的。但是您必须编写一些删除脚本标签的逻辑。看看这里:stackoverflow.com/questions/14708189/…
private scripts: {ScriptModel}[] = []; 行有错误。它应该是 private scripts: ScriptModel[] = [];
@d123546 你应该只初始化一次服务。使用 @Injectable({providedIn: 'root'}) 并将服务 only 添加到 app.module.tsproviders:[] 数组中
A
Aswin Sanakan

您可以在 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 数组。


我不得不说,这看起来很简单,但效果很好,很容易实现:-)
这种类型的脚本注入的问题在于,由于 Angular 是一个 SPA,即使您从 DOM 中删除了 scripts 标签,这些脚本也基本上保留在浏览器缓存中。
真棒答案!拯救了我的一天,它适用于 Angular 6 和 7。经过测试!
要将数据发送到外部 js 并从外部 js 获取数据,请在 app.component.ts 中调用此函数,并在任何功能级别模块的组件中使用 declare var d3Sankey:any。它正在工作。
这对我使用 Angular 13 有效。非常感谢!
H
Hetdev

您好,您只需几行代码即可使用 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() 中进行调用,这非常有用


E
Eduardo Vargas

我已经用新的渲染器 api 完成了这个代码片段

 constructor(private renderer: Renderer2){}

 addJsToElement(src: string): HTMLScriptElement {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = src;
    this.renderer.appendChild(document.body, script);
    return script;
  }

然后像这样称呼它

this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
        console.log('SkyScanner Tag loaded');
} 

StackBlitz


A
Andrew Brēza

我有一个动态加载脚本的好方法!现在我在我的项目中使用 ng6, echarts4 (>700Kb ) ,ngx-echarts3 。当我通过 ngx-echarts 的文档使用它们时,我需要在 angular.json 中导入 echarts : "scripts":["./node_modules/echarts/dist/echarts.min.js"] 因此在登录模块中,加载脚本时的页面.js,这是一个大文件!我不想要它。

所以,我认为 Angular 将每个模块加载为一个文件,我可以插入一个路由器解析器来预加载 js,然后开始加载模块!

// PreloadScriptResolver.service.js

/**动态加载js的服务 */
@Injectable({
  providedIn: 'root'
})
export class PreloadScriptResolver implements Resolve<IPreloadScriptResult[]> {
  // Here import all dynamically js file
  private scripts: any = {
    echarts: { loaded: false, src: "assets/lib/echarts.min.js" }
  };
  constructor() { }
  load(...scripts: string[]) {
    const promises = scripts.map(script => this.loadScript(script));
    return Promise.all(promises);
  }
  loadScript(name: string): Promise<IPreloadScriptResult> {
    return new Promise((resolve, reject) => {
      if (this.scripts[name].loaded) {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      } else {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = this.scripts[name].src;
        script.onload = () => {
          this.scripts[name].loaded = true;
          resolve({ script: name, loaded: true, status: 'Loaded' });
        };
        script.onerror = (error: any) => reject({ script: name, loaded: false, status: 'Loaded Error:' + error.toString() });
        document.head.appendChild(script);
      }
    });
  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<IPreloadScriptResult[]> {
   return this.load(...route.routeConfig.data.preloadScripts);
  }
}

然后在 submodule-routing.module.ts 中,导入这个 PreloadScriptResolver:

const routes: Routes = [
  {
    path: "",
    component: DashboardComponent,
    canActivate: [AuthGuardService],
    canActivateChild: [AuthGuardService],
    resolve: {
      preloadScripts: PreloadScriptResolver
    },
    data: {
      preloadScripts: ["echarts"]  // important!
    },
    children: [.....]
}

这段代码运行良好,它承诺:加载js文件后,模块开始加载!这个解析器可以在许多路由器中使用


您能否分享 IPreloadScriptResult 接口的代码?
g
grg

Angular 通用解决方案;在加载脚本以播放视频之前,我需要等待特定元素出现在页面上。

import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from "@angular/common";

@Injectable({
  providedIn: 'root'
})
export class ScriptLoaderService {

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
  ) {
  }

  load(scriptUrl: string) {
    if (isPlatformBrowser(this.platformId)) {
      let node: any = document.createElement('script');
      node.src = scriptUrl;
      node.type = 'text/javascript';
      node.async = true;
      node.charset = 'utf-8';
      document.getElementsByTagName('head')[0].appendChild(node);
    }
  }
}

这不知何故感觉很狡猾
b
bob.mazzo

就我而言,我已经使用上述技术加载了 jscss visjs 文件——效果很好。我从 ngOnInit() 调用新函数

注意:我可以通过简单地将 <script><link> 标记添加到 html 模板文件来加载它。

loadVisJsScript() { console.log('正在加载 visjs js/css 文件...');让 script = document.createElement('script'); script.src = "../../assets/vis/vis.min.js"; script.type = '文本/javascript'; script.async = true; script.charset = 'utf-8'; document.getElementsByTagName('head')[0].appendChild(script);让链接 = document.createElement("链接"); link.type = "样式表"; link.href = "../../assets/vis/vis.min.css"; document.getElementsByTagName('head')[0].appendChild(link); }


我想从外部网络源加载 CSS 文件。目前,我正面临“不允许加载本地资源”的错误。请提出建议。
我试过这种方式。尽管它适用于脚本文件,但它不适用于样式表
F
F.H.

对于那些也想动态加载样式的人。 (基于@Rahul Kumar 的精彩回答)

脚本.store.ts

interface Scripts {
    name: string;
    src: string;
}

export const StyleStore: Scripts[] = [
    { name: 'fancybox-css', src: 'https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css' }
];

export const ScriptStore: Scripts[] = [
    { name: 'jquery', src: 'https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js' },
    { name: 'other', src: '[other script source]'}
];

脚本加载器.service.ts

import { Injectable } from '@angular/core';
import { ScriptStore, StyleStore } from '../../stores/script.store';

@Injectable({
  providedIn: 'root'
})
export class ScriptLoaderService {

  private scripts: any = {};
  private styles: any = {};

  constructor() {
    ScriptStore.forEach((script: any) => {
      this.scripts[script.name] = {
        loaded: false,
        src: script.src
      };
    });

    StyleStore.forEach((script: any) => {
      this.styles[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);
  }

  loadStyles(...scripts: string[]) {
    var promises: any[] = [];
    scripts.forEach((script) => promises.push(this.loadStyle(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') as any;
        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);
      }
    });
  }

  loadStyle(name: string) {
    return new Promise((resolve, reject) => {
      //resolve if already loaded
      if (this.styles[name].loaded) {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      }
      else {
        //load style
        let style = document.createElement('link') as any;
        style.type = "text/css";
        style.rel = "stylesheet";
        style.href = this.styles[name].src;
        if (style.readyState) {  //IE
          style.onreadystatechange = () => {
            if (style.readyState === "loaded" || style.readyState === "complete") {
              style.onreadystatechange = null;
              this.styles[name].loaded = true;
              resolve({ style: name, loaded: true, status: 'Loaded' });
            }
          };
        } else {  //Others
          style.onload = () => {
            this.styles[name].loaded = true;
            resolve({ style: name, loaded: true, status: 'Loaded' });
          };
        }
        style.onerror = (error: any) => resolve({ style: name, loaded: false, status: 'Loaded' });
        document.getElementsByTagName('head')[0].appendChild(style);
      }
    });
  }

}

app.component.ts

constructor(private scriptLoaderService: ScriptLoaderService) {
  this.scriptLoaderService.loadStyles('fancybox-css').then(x => {
    this.scriptLoaderService.load('jquery', 'fancybox').then(data => {
    }).catch(error => console.log(error));
  });
}

Z
Z3nk

@rahul-kumar 的解决方案对我很有用,但我想在我的打字稿中调用我的 javascript 函数

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


如果我导入多个脚本 foo,foo1,foo2 并且我只知道在执行期间如何动态地进行声明?
s
shalitha anuradha

我在下面的链接中遇到了同样的问题。我以一种非常简单的方式解决了它。

https://www.gstatic.com/charts/loader.js

我需要在下面的代码中访问 google 变量。但是当我把它放在角度类中时它不起作用。

google.charts.load("current", {packages:['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
    var data = google.visualization.arrayToDataTable([
        ["Element", "Density", { role: "style" } ],
        ["Copper", 8.94, "dodgerblue"],
        ["Silver", 10.49, "dodgerblue"],
        ["Gold", 19.30, "dodgerblue"],
        ["Platinum", 21.45, "color: dodgerblue"]
    ]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1,
    { calc: "stringify",
        sourceColumn: 1,
        type: "string",
        role: "annotation" },
    2]);

var options = {
    title: "Density of Precious Metals, in g/cm^3",
    width: 600,
    height: 400,
    bar: {groupWidth: "50%"},
    legend: { position: "none" },
};
var chart = new google.visualization.ColumnChart(document.getElementById("columnchart_values"));
chart.draw(view, options);

}

我在 ts 类的顶部创建了一个具有相同名称(google)的全局变量,然后该变量自动引用所需的变量。(因为它是全局范围)然后问题就解决了。

declare var google: any;

W
Waseem

@d123546 我遇到了同样的问题,现在在组件中使用 ngAfterContentInit (Lifecycle Hook) 让它工作,如下所示:

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));
}

a
azerafati

我发现这个解决方案更干净,首先在您的模块中导入 HttpClientJsonpModule,然后执行类似这样的操作

this.apiLoaded = this.httpClient.jsonp(environment.AnyApiUrl, 'callback')
  .pipe(
    map(() => true),
    catchError(() => of(false)),
  );

在您的模板中:

<app-component *ngIf="apiLoaded | async"></app-component>

该解决方案在官方 Angular Google Maps 文档 here 中。


o
otsili

Angular 具有防止用户直接干扰 html 输出的逻辑。所以你必须让 Angular 通过在 angular.json 文件中给出该方向来注入标签。

首先,您必须获取脚本文件。有两种方法:

下载脚本文件(例如 somelibrary.js)

将其放在资产文件夹中

将脚本的相对路径放入 angular.json 文件的“脚本”部分:

"scripts": [
  "src/assets/somelibrary.js"
]

使用 npm/yarn 安装脚本:

将脚本的相对路径放入 angular.json 文件的“脚本”部分:

"scripts": [
  "./node_modules/somelibrary/dist/somelibrary.min.js"
]

u
unos baghaii

一个样本可以是

script-loader.service.ts 文件

import {Injectable} from '@angular/core';
import * as $ from 'jquery';

declare let document: any;

interface Script {
  src: string;
  loaded: boolean;
}

@Injectable()
export class ScriptLoaderService {
public _scripts: Script[] = [];

/**
* @deprecated
* @param tag
* @param {string} scripts
* @returns {Promise<any[]>}
*/
load(tag, ...scripts: string[]) {
scripts.forEach((src: string) => {
  if (!this._scripts[src]) {
    this._scripts[src] = {src: src, loaded: false};
  }
});

let promises: any[] = [];
scripts.forEach((src) => promises.push(this.loadScript(tag, src)));

return Promise.all(promises);
}

 /**
 * Lazy load list of scripts
 * @param tag
 * @param scripts
 * @param loadOnce
 * @returns {Promise<any[]>}
 */
loadScripts(tag, scripts, loadOnce?: boolean) {
loadOnce = loadOnce || false;

scripts.forEach((script: string) => {
  if (!this._scripts[script]) {
    this._scripts[script] = {src: script, loaded: false};
  }
});

let promises: any[] = [];
scripts.forEach(
    (script) => promises.push(this.loadScript(tag, script, loadOnce)));

return Promise.all(promises);
}

/**
 * Lazy load a single script
 * @param tag
 * @param {string} src
 * @param loadOnce
 * @returns {Promise<any>}
 */
loadScript(tag, src: string, loadOnce?: boolean) {
loadOnce = loadOnce || false;

if (!this._scripts[src]) {
  this._scripts[src] = {src: src, loaded: false};
}

return new Promise((resolve, reject) => {
  // resolve if already loaded
  if (this._scripts[src].loaded && loadOnce) {
    resolve({src: src, loaded: true});
  }
  else {
    // load script tag
    let scriptTag = $('<script/>').
        attr('type', 'text/javascript').
        attr('src', this._scripts[src].src);

    $(tag).append(scriptTag);

    this._scripts[src] = {src: src, loaded: true};
    resolve({src: src, loaded: true});
  }
 });
 }
 }

和使用

第一次注入

  constructor(
  private _script: ScriptLoaderService) {
  }

然后

ngAfterViewInit()  {
this._script.loadScripts('app-wizard-wizard-3',
['assets/demo/default/custom/crud/wizard/wizard.js']);

}

或者

    this._script.loadScripts('body', [
  'assets/vendors/base/vendors.bundle.js',
  'assets/demo/default/base/scripts.bundle.js'], true).then(() => {
  Helpers.setLoading(false);
  this.handleFormSwitch();
  this.handleSignInFormSubmit();
  this.handleSignUpFormSubmit();
  this.handleForgetPasswordFormSubmit();
});

A
Aniket
import { Injectable } from '@angular/core';
import * as $ from 'jquery';

interface Script {
    src: string;
    loaded: boolean;
}

@Injectable()
export class ScriptLoaderService {
    public _scripts: Script[] = [];

    /**
     * @deprecated
     * @param tag
     * @param {string} scripts
     * @returns {Promise<any[]>}
     */
    load(tag, ...scripts: string[]) {
        scripts.forEach((src: string) => {
            if (!this._scripts[src]) {
                this._scripts[src] = { src: src, loaded: false };
            }
        });

        const promises: any[] = [];
        scripts.forEach(src => promises.push(this.loadScript(tag, src)));

        return Promise.all(promises);
    }

    /**
     * Lazy load list of scripts
     * @param tag
     * @param scripts
     * @param loadOnce
     * @returns {Promise<any[]>}
     */
    loadScripts(tag, scripts, loadOnce?: boolean) {
        debugger;
        loadOnce = loadOnce || false;

        scripts.forEach((script: string) => {
            if (!this._scripts[script]) {
                this._scripts[script] = { src: script, loaded: false };
            }
        });

        const promises: any[] = [];
        scripts.forEach(script => promises.push(this.loadScript(tag, script, loadOnce)));

        return Promise.all(promises);
    }

    /**
     * Lazy load a single script
     * @param tag
     * @param {string} src
     * @param loadOnce
     * @returns {Promise<any>}
     */
    loadScript(tag, src: string, loadOnce?: boolean) {
        debugger;
        loadOnce = loadOnce || false;

        if (!this._scripts[src]) {
            this._scripts[src] = { src: src, loaded: false };
        }

        return new Promise((resolve, _reject) => {
            // resolve if already loaded
            if (this._scripts[src].loaded && loadOnce) {
                resolve({ src: src, loaded: true });
            } else {
                // load script tag
                const scriptTag = $('<script/>')
                    .attr('type', 'text/javascript')
                    .attr('src', this._scripts[src].src);

                $(tag).append(scriptTag);

                this._scripts[src] = { src: src, loaded: true };
                resolve({ src: src, loaded: true });
            }
        });
    }

    reloadOnSessionChange() {
        window.addEventListener('storage', function(data) {
            if (data['key'] === 'token' && data['oldValue'] == null && data['newValue']) {
                document.location.reload();
            }
        });
    }
}

A
Anis KCHAOU

这个解决方案对我有用:

1 ) 创建一个名为 URLLoader 的新类

export class URLLoader {
  constructor() {

  }

  loadScripts() {

    const dynamicScripts = [
      'URL 1',
      'URL 2',
      'URL n'
    ];

    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('app-root')[0].appendChild(node);
    }
  }

}

) 扩展类 URLLoader 并从组件类调用 loadScripts 方法

export class AppComponent extends URLLoader implements OnInit {  

   constructor(){}

   ngOnInit() {
   super.loadScripts();
   }

}

S
Satyam Dorville

您可以使用 Google 跟踪代码管理器来管理您的外部脚本,而无需进入代码。对于非技术用户和技术用户来说,这是一个完美的解决方案。