从 Angular 组件动态加载外部 javascript 文件
Dynamically load external javascript file from Angular component
我正在使用 Angular 4 和 CLI 创建一个 Angular 应用程序。我正在尝试将 SkyScanner 搜索小部件添加到我的组件之一。
部分实现需要添加新的外部脚本:
<script src="https://widgets.skyscanner.net/widget-server/js/loader.js" async></script>
我不确定引用此文件的正确方法。如果我将脚本添加到我的 index.html 文件中,则除非执行整页刷新,否则不会加载小部件。我假设脚本试图在加载时操纵 DOM,并且脚本运行时元素不存在。
仅在加载包含 Skyscanner 小部件的组件时才加载脚本的正确方法是什么?
将 loader.js
添加到您的资产文件夹,然后在您的 angular-cli.json
"scripts": ["./src/assets/loader.js",]
然后将其添加到您的 typings.d.ts
declare var skyscanner:any;
你将能够使用它
skyscanner.load("snippets","2");
尝试在组件负载上加载外部 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);
}
}
}
你可以做一件事
如果你有 angular-cli.json
然后你可以向脚本声明
喜欢
"scripts": ["../src/assets/js/loader.js"]
然后在你的组件中声明 skyscanner
喜欢
declare var skyscanner:any;
就是这样!
希望对您有所帮助
我已经完成了这段代码
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;
}
您可以创建自己的指令来加载如下脚本
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>
注意:这是专门针对外部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;
然后在您的 class 中调用您需要的方法。例如
//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);
}
}
如果你没有得到我的代码,那么也试试这个 。
希望对您有所帮助!
我有同样的问题,但在我的例子中,我在 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 -->
如前所述,它没有用。然后,我找到了对我有帮助的东西:
删除 app.component.html 中的脚本调用。您必须在 app.component.ts 文件中 link 这些脚本。
在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);
}
}
对我有用。我用的是Angular6,希望对你有帮助。
有点晚了,但我更喜欢这样做(服务方式)....
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
已接受的答案是正确的,但无法使用,因为浏览器在下载脚本后需要更多时间来解析脚本。因此,如果从加载的脚本中使用任何变量,则需要在新创建的 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;
}
经过这么多次代码试验,这对我很有效
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);
});
}
在我的例子中,我不得不加载几个相互依赖的不同文件(某种使用 bootstrap 然后使用 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);
}));
}
}
由于源 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>
现在我们已经将自定义事件发送到 window 对象,我们可以使用我们组件中提供的装饰器 @HostListener
Angular 来监听它。
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
}
);
}
我正在使用 Angular 4 和 CLI 创建一个 Angular 应用程序。我正在尝试将 SkyScanner 搜索小部件添加到我的组件之一。
部分实现需要添加新的外部脚本:
<script src="https://widgets.skyscanner.net/widget-server/js/loader.js" async></script>
我不确定引用此文件的正确方法。如果我将脚本添加到我的 index.html 文件中,则除非执行整页刷新,否则不会加载小部件。我假设脚本试图在加载时操纵 DOM,并且脚本运行时元素不存在。
仅在加载包含 Skyscanner 小部件的组件时才加载脚本的正确方法是什么?
将 loader.js
添加到您的资产文件夹,然后在您的 angular-cli.json
"scripts": ["./src/assets/loader.js",]
然后将其添加到您的 typings.d.ts
declare var skyscanner:any;
你将能够使用它
skyscanner.load("snippets","2");
尝试在组件负载上加载外部 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);
}
}
}
你可以做一件事
如果你有 angular-cli.json
然后你可以向脚本声明
喜欢
"scripts": ["../src/assets/js/loader.js"]
然后在你的组件中声明 skyscanner
喜欢
declare var skyscanner:any;
就是这样!
希望对您有所帮助
我已经完成了这段代码
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;
}
您可以创建自己的指令来加载如下脚本
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>
注意:这是专门针对外部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;
然后在您的 class 中调用您需要的方法。例如
//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);
}
}
如果你没有得到我的代码,那么也试试这个
希望对您有所帮助!
我有同样的问题,但在我的例子中,我在 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 -->
如前所述,它没有用。然后,我找到了对我有帮助的东西:
删除 app.component.html 中的脚本调用。您必须在 app.component.ts 文件中 link 这些脚本。
在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);
}
}
对我有用。我用的是Angular6,希望对你有帮助。
有点晚了,但我更喜欢这样做(服务方式)....
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
已接受的答案是正确的,但无法使用,因为浏览器在下载脚本后需要更多时间来解析脚本。因此,如果从加载的脚本中使用任何变量,则需要在新创建的 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;
}
经过这么多次代码试验,这对我很有效
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);
});
}
在我的例子中,我不得不加载几个相互依赖的不同文件(某种使用 bootstrap 然后使用 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);
}));
}
}
由于源 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>
现在我们已经将自定义事件发送到 window 对象,我们可以使用我们组件中提供的装饰器 @HostListener
Angular 来监听它。
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
}
);
}