从 Angular 组件动态加载外部 javascript 文件
Posted
技术标签:
【中文标题】从 Angular 组件动态加载外部 javascript 文件【英文标题】:Dynamically load external javascript file from Angular component 【发布时间】:2017-10-27 12:22:15 【问题描述】:我正在使用 Angular 4 和 CLI 创建一个 Angular 应用程序。我正在尝试将 SkyScanner 搜索小部件添加到我的一个组件中。
Skyscanner Widget Example
部分实现需要添加新的外部脚本:
<script src="https://widgets.skyscanner.net/widget-server/js/loader.js" async></script>
我不确定引用此文件的正确方法。如果我将脚本添加到我的 index.html 文件中,则除非执行整页刷新,否则不会加载小部件。我假设脚本尝试在加载时操作 DOM,并且脚本运行时元素不存在。
只有在加载包含 Skyscanner 小部件的组件时才加载脚本的正确方法是什么?
【问题讨论】:
***.com/questions/34489916/… 【参考方案1】:尝试在组件加载时加载外部 javascript,如下所示:
loadAPI: Promise<any>;
constructor()
this.loadAPI = new Promise((resolve) =>
this.loadScript();
resolve(true);
);
public loadScript()
var isFound = false;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i)
if (scripts[i].getAttribute('src') != null && scripts[i].getAttribute('src').includes("loader"))
isFound = true;
if (!isFound)
var dynamicScripts = ["https://widgets.skyscanner.net/widget-server/js/loader.js"];
for (var i = 0; i < dynamicScripts.length; i++)
let 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);
【讨论】:
这看起来是迄今为止最好的答案。谢谢你。对 loadScript 的调用应该在构造函数中还是在 ngOnInit 中更好? 但是这个文件加载会延迟,所以我的函数在没有这些知识的情况下呈现。后来它加载没有用。如何解决这个问题。 @karthik.. 如果 dom 中已经存在,该检查是为了避免重复文件加载 我试过这个......它只适用于重新加载......例如。如果您要从一个组件导航到另一个组件。它会在第一次冻结,重新加载后页面将与动态脚本一起正确显示 @GoutamBSeervi 你有没有找到任何延迟加载的解决方案。我也面临同样的问题..【参考方案2】:我遇到了同样的问题,但在我的例子中,我在 html 文件的末尾导入了 10 个库,这些库有很多方法、侦听器、事件等等,而在我的例子中,我没有不需要专门调用方法。
我所拥有的例子:
<!-- app.component.html -->
<div>
...
</div>
<script src="http://www.some-library.com/library.js">
<script src="../assets/js/my-library.js"> <!-- a route in my angular project -->
如前所述,它不起作用。然后,我找到了一些对我有帮助的东西:Milad response
删除 app.component.html 中的脚本调用。您必须在 app.component.ts 文件中链接这些脚本。
在ngOnInit()中,使用方法来追加库,例如:
``
<!-- app.component.ts -->
export class AppComponent implements OnInit
title = 'app';
ngOnInit()
this.loadScript('http://www.some-library.com/library.js');
this.loadScript('../assets/js/my-library.js');
public loadScript(url: string)
const body = <HTMLDivElement> document.body;
const script = document.createElement('script');
script.innerHTML = '';
script.src = url;
script.async = false;
script.defer = true;
body.appendChild(script);
它对我有用。我使用 Angular 6,希望对您有所帮助。
【讨论】:
太棒了! 太棒了,花了几个小时试图完成这项工作,这是最简单的解决方案 我真的真的真的很感激,你拯救了我的一天,这工作非常好 如何在另一个组件中使用加载的库? 找了好久终于找到方法了,太有帮助了,非常感谢!!!【参考方案3】:我已经完成了这段代码sn-p
addJsToElement(src: string): HTMLScriptElement
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
this.elementRef.nativeElement.appendChild(script);
return script;
然后这样称呼它
this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () =>
console.log('SkyScanner Tag loaded');
编辑:使用新的渲染器 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;
StackBlitz
【讨论】:
这里的element.ref
是什么?
嗨 pardeep 这是对 body 标签的引用,使用新的渲染器 api 可以重写它。检查我的编辑。
如果您想知道,新的渲染器是:angular.io/api/core/Renderer2
感谢@emp 的澄清【参考方案4】:
将loader.js
添加到您的资产文件夹,然后在您的angular-cli.json
中
"scripts": ["./src/assets/loader.js",]
然后将其添加到您的typings.d.ts
declare var skyscanner:any;
你可以使用它
skyscanner.load("snippets","2");
【讨论】:
我添加的最后一行 (skyscanner.load...) 是否添加到组件中?假设是这样,我看到一个错误,说找不到名称 skyscanner。 如何将它用于不同的构建环境? IE。根据是集成、测试还是生产构建加载不同的脚本?【参考方案5】:您可以创建自己的指令来加载脚本,如下所示
import Directive, OnInit, Input from '@angular/core';
@Directive(
selector: '[appLoadScript]'
)
export class LoadScriptDirective implements OnInit
@Input('script') param: any;
ngOnInit()
let node = document.createElement('script');
node.src = this.param;
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
您可以在组件模板中的任何位置使用它,如下所示
<i appLoadScript [script]="'script_file_path'"></i>
例如,要在组件中动态加载 JQuery,请在组件的模板中插入以下代码
<i appLoadScript [script]="'/assets/baker/js/jquery.min.js'"></i>
【讨论】:
如果脚本需要参数? :) @Ismaestro 它是一个指令,您可以随脚本传递任意数量的参数。甚至脚本也是该指令的参数。【参考方案6】:接受的答案是正确的,但不起作用,因为浏览器在下载脚本后需要更多时间来解析脚本。因此,如果从加载的脚本中使用了任何变量,则需要在新创建的 html 脚本元素的 onload 事件上使用它。如下所述,我已经改进了接受的答案-
loadAPI: Promise<any>;
constructor()
this.loadAPI = new Promise((resolve) =>
let node = this.loadScript();
if (node)
node.onload = () =>
resolve(true);
;
else
resolve(true);
);
ngOnInit()
this.loadAPI
.then((flag) =>
//Do something when script is loaded and parsed by browser
);
loadScript()
let node = undefined;
let isFound = false;
const scripts = document.getElementsByTagName('script')
for (let i = 0; i < scripts.length; ++i)
// Check if script is already there in html
if (scripts[i].getAttribute('src') != null && scripts[i].getAttribute('src').includes("loader"))
isFound = true;
if (!isFound)
const dynamicScript = 'https://widgets.skyscanner.net/widget-server/js/loader.js';
node = document.createElement('script');
node.src = dynamicScript;
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(node);
return node;
return node;
【讨论】:
感谢脚本的更改,它对我很有用。但我有一个问题。我要包含的来自贝宝的脚本加载另一个 js 文件,然后在 127.0.0.1:4200 处搜索该文件(但未找到)。有没有办法让 require.js 的二次加载正常工作?【参考方案7】:你可以做一件事
如果你有 angular-cli.json
然后你可以声明到脚本
喜欢
"scripts": ["../src/assets/js/loader.js"]
然后在你的组件中声明skyscanner
喜欢
declare var skyscanner:any;
就是这样!
希望对你有帮助
【讨论】:
我已在本地添加脚本并将其包含在 angular-cli.json 文件中。不幸的是,这不起作用。请您解释一下将声明添加到组件中需要做什么? 对于 Angular 8,我认为 7,angular-cli.json 现在是 angular.json 并且在数组中放置一个外部脚本会使 angular 启动崩溃。 这是每个文档的格式,它不会使应用程序崩溃: "input": "src/external-module/main.js", "inject": false, "bundleName" :“外部模块”angular.io/guide/…【参考方案8】:注意:这是专门针对外部js链接的! 步骤 1. 建议将 Angular 脚本添加到正文底部的 index.html 文件中!我尝试了所有其他方法,但都失败了。
<!-- File Name: index.html and its inside src dir-->
<body class="">
<app-root></app-root>
<!-- Icons -->
<script src="https://unpkg.com/feather-icons/dist/feather.min.js"></script>
</body>
接下来有两种方法可以做到这一点... 在 Anguar5 中使用此代码顶部类型的组件文件夹中
declare var feather:any;
然后在你的类中调用你需要的方法。例如
//FileName: dashboard.component.ts
import Component, OnInit from '@angular/core';
declare var feather:any;
export class DashboardComponent implements OnInit
ngOnInit()
feather.replace();
这应该运行你的代码! 另一种可能适用于旧版本的方式。我没查过!
//FileName: dashboard.component.ts
import Component, OnInit from '@angular/core';
export class DashboardComponent implements OnInit
ngOnInit()
let node = document.createElement('script');
node.innerText='feather.replace()';
node.type = 'text/javascript';
node.async = false;
node.charset = 'utf-8';
document.getElementsByTagName('body')[0].appendChild(node);
如果你没有得到我的代码,那么也试试这个link。
希望这会有所帮助!
【讨论】:
【参考方案9】:有点晚了,但我更喜欢这样做(服务方式)....
import Injectable from '@angular/core';
import Observable from "rxjs";
interface Scripts
name: string;
src: string;
export const ScriptStore: Scripts[] = [
name: 'script-a', src: 'assets/js/a.js' ,
name: 'script-b', src: 'assets/js/b.js' ,
name: 'script-c', src: 'assets/js/c.js'
];
declare var document: any;
@Injectable()
export class FileInjectorService
private scripts: any = ;
constructor()
ScriptStore.forEach((script: any) =>
this.scripts[script.name] =
loaded: false,
src: script.src
;
);
loadJS(...scripts: string[])
const promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadJSFile(script)));
return Promise.all(promises);
loadJSFile(name: string)
return new Promise((resolve, reject) =>
if (!this.scripts[name].loaded)
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
if (script.readyState)
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
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);
else
resolve( script: name, loaded: true, status: 'Already Loaded' );
);
然后在我的组件中,我可以执行以下操作:
ngOnInit()
this.fileInjectorService.loadJS('script-a', 'script-c').then(data =>
// Loaded A and C....
).catch(error => console.log(error));
在 Angular 6/7 中测试
【讨论】:
应该参考原文-medium.com/@zainzafar/…【参考方案10】:经过这么多代码试验,这对我有用
ngOnInit()
this.loadFormAssets().then(() => console.log("Script Loaded");).catch(() => console.log("Script Problem"););
public loadFormAssets()
return new Promise(resolve =>
const scriptElement = document.createElement('script');
scriptElement.src =this.urls.todojs;
scriptElement.onload = resolve;
document.body.appendChild(scriptElement);
const scriptElement1 = document.createElement('script');
scriptElement1.src =this.urls.vendorjs;
scriptElement1.onload = resolve;
document.body.appendChild(scriptElement1);
);
【讨论】:
【参考方案11】:在我的情况下,我必须加载一些相互依赖的不同文件(某种使用引导程序的东西,然后使用 jquery 插件,然后使用 jquery)并且它们都在加载时立即初始化,假设它们已加载在网页上同步。所有其他答案都假设您正在加载完全不相关的文件(或在所有内容加载后等待您初始化) - 而我的设置会引发各种缺少变量的问题。
我的解决方案是创建一个Promise
链(而不是像@carlitoxenlaweb 那样的Promise
列表 - 这将并行解决所有问题),以便下一个文件仅在前一个文件完成初始化后加载:
private myScripts = [
'/assets/js/jquery-2.2.4.min.js',
'/assets/js/bootstrap.min.js',
'/assets/js/jquery.bootstrap.js',
'/assets/js/jquery.validate.min.js',
'/assets/js/somescript.js',
];
private loadScripts()
let container:HTMLElement = this._el.nativeElement;
let promise = Promise.resolve();
for (let url of this.myScripts)
promise = promise.then(_ => new Promise((resolve, reject) =>
let script = document.createElement('script');
script.innerHTML = '';
script.src = url;
script.async = true;
script.defer = false;
script.onload = () => resolve();
script.onerror = (e) => reject(e);
container.appendChild(script);
));
【讨论】:
即使文件位于 node_modules 文件夹中,此方法是否有效? 我猜这不会起作用 - 这种加载发生在node_modules
文件夹不存在的运行时。您可以从要使用的模块中复制相关文件,但可能最好使用另一种方法 - 例如将模块导入打字稿文件(然后将被编译)。
是的,我尝试从 node_modules 加载,但没有成功。我别无选择,只能将所需的东西复制到 assets js 文件夹中并从中加载。【参考方案12】:
由于源 URL 允许您调用全局函数,您可以使用它来设置自定义事件处理程序。
index.html
<script
type="text/javascript"
src="http://www.bing.com/api/maps/mapcontrol?callback=onBingLoaded&branch=release"
async defer
></script>
<script>
function onBingLoaded()
const event = new CustomEvent("bingLoaded");
window.dispatchEvent(event);
</script>
现在我们已经将自定义事件分派给了窗口对象,我们可以使用 Angular 在我们的组件中提供的装饰器 @HostListener
来监听它。
app.component.ts
export class AppComponent
@ViewChild('mapCanvas')
mapCanvas!: ElementRef;
private map!: Microsoft.Maps.Map;
@HostListener('window:bingLoaded', ['$event'])
defineMapCanvas()
this.map = new Microsoft.Maps.Map(
this.mapCanvas.nativeElement,
credentials: [YOUR API KEY HERE],
...other options
);
参考:https://angular.io/api/core/HostListener
【讨论】:
以上是关于从 Angular 组件动态加载外部 javascript 文件的主要内容,如果未能解决你的问题,请参考以下文章
如何在作为 innerHtml 加载的文档的子部分中动态加载 Angular 组件?
angular 2组件无法从外部css皮肤加载相对背景图像url