<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加载动态编写脚本吗?
#1 楼
如果使用的是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是装载机的未来。
– CallMeLaNN
2015年12月28日在10:31
Sublime Text的TypeScript插件对TypeScript代码中的System不满意:在编译和运行期间找不到名称“ System”,但没有错误。 Angular2和系统脚本文件均已添加到index.html中。无论如何要导入系统并使插件满意?
– CallMeLaNN
15年12月29日在5:34
但是,如果您不使用system.js,该怎么办!
–Murhaf Sousli
16年1月9日在2:26
@drewmoore我不记得我为什么这么说了,require()/ import应该可以很好地作为您的答案,+ 1
–Murhaf Sousli
16 Apr 15'在8:37
据我所知,这些选项不适用于Internet上的脚本,仅适用于本地文件。这似乎并不能回答最初提出的问题。
– WillyC
17年5月2日在21:26
#2 楼
您可以使用以下技术在Angular项目中按需动态加载JS脚本和库。
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复制为它is
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以上-我有很多库!
–Our_Benefactors
17年6月1日在20:41
看来我讲得太早了。此解决方案在iOS Safari中不起作用。
–Our_Benefactors
17年9月9日在18:24
加载脚本后,检查脚本是否附加到您的dom的头部
–拉胡尔·库马尔(Rahul Kumar)
17年10月10日在5:06
您如何调用js文件中的函数?它们是否连接到全局窗口?
– pdiddy
19-09-23在12:47
我修改了加载功能以进行顺序加载。这是我所做的。 load(... scripts:string []){让诺言= Promise.resolve(); scripts.forEach(script => {promise = promise.then(()=> this.loadScript(script))。then(res => console.log(res));});}谢谢您的回答。
–威廉
7月29日18:45
#3 楼
这可能有效。此代码将<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);
}
}
评论
谢谢男人为我工作。现在,您也可以使用ngonit方法将其加载到页面加载中。因此,不需要单击按钮。
– Niraj
16-09-19在12:11
我正在使用相同的方式..但对我没有用。你能帮我吗? this.loadJs(“ javascriptfile path”);公共loadjs(file){让节点= document.createElement('script'); node.src =文件; node.type ='文本/ javascript'; node.async = true; node.charset ='utf-8'; document.getElementsByTagName('head')[0] .appendChild(node); }
– Mitesh Shah
16-10-22在8:14
您是要在点击还是加载页面时执行此操作?您能否告诉我们更多有关结果或错误的信息?
– ng-darren
16-10-25在0:48
您好,我已经尝试实现此功能并且工作正常,但是新的Promise尚未解决。你能告诉我Promise我必须进口什么吗?
–user3145373ツ
16 Dec 15'在5:23
嗨,我使用Promise时没有导入任何特殊内容。
– ng-darren
16 Dec 16'在7:07
#4 楼
我已经修改了@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
}
评论
您忘记将异步设置为true。
–黄会
17年6月3日在16:34
我有一个问题,当我们追加脚本并在路线之间导航时,可以说我将脚本加载到主页上并浏览到并返回到主页,是否可以重新初始化当前脚本?意味着我想删除以前加载的一个并重新加载?非常感谢你
– d123546
17年6月16日在9:40
@ d123546是的,如果您希望这样做是可能的。但是您必须编写一些逻辑来删除脚本标签。在这里看看:stackoverflow.com/questions/14708189/…
–乔尔
17年6月16日在9:42
感谢Joel,但是有什么方法可以删除脚本初始化的函数吗?目前我在我的angular4项目中遇到了一个问题,因为我正在加载脚本,该脚本初始化了jquery滑块,因此当第一个路线加载时,它显示正确,但是当我导航到其他路线并返回到我的路线时,脚本被加载了两次并且滑块正在不再加载:(您能给我建议吗
– d123546
17年6月16日在10:18
专用脚本行中有错误:{ScriptModel} [] = [];。它应该是私有脚本:ScriptModel [] = [];
–克里斯·海恩斯(Chris Haines)
19年1月28日在16:11
#5 楼
另一个选择是利用scriptjs
软件包解决此问题,允许您从任何URL按需加载脚本资源。
安装软件包:
npm i scriptjs
和
scriptjs
的类型定义:npm install --save @types/scriptjs
然后导入
$script.get()
方法:import { get } from 'scriptjs';
,最后加载脚本资源,在本例中为Google Maps库:
export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}
演示
评论
很好,但是应该不是npm install --save @ types / scriptjs,而是npm install --save-dev @ types / scriptjs(或者只是npm i -D @ types / scriptjs)
–Youness Houdass
6月29日7:27
#6 楼
您可以在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
数组。评论
我不得不说,这看起来很简单,但是效果很好,很容易实现:-)
–皮埃尔
18年6月7日在14:01
这种脚本注入的问题在于,由于Angular是SPA,因此即使从DOM中删除了scripts标记,该脚本也基本上保留在浏览器缓存中。
– j4rey
18/12/13在7:49
很棒的答案!保存了我的一天,它可与Angular 6和7一起使用。
– Nadeeshan Herath
19年2月13日在12:35
要将数据发送到外部js并从外部js获取数据,请在app.component.ts上调用此函数,并在任何功能级别模块的组件上使用声明var d3Sankey:any。工作正常
– gnganapath
19年11月12日在19:49
#7 楼
我已经使用新的渲染器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
#8 楼
嗨,您可以仅使用几行代码来使用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()中进行调用,这将非常有用。#9 楼
我有一个动态加载脚本的好方法!现在我在项目中使用ng6,echarts4(> 700Kb),ngx-echarts3。当我由ngx-echarts的文档使用它们时,我需要在angular.json中导入echarts:
“ scripts”:[“ ./ node_modules / echarts / dist / echarts.min.js”]
因此登录模块,页面在加载scripts.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接口的代码?
–穆勒·巴拉拉(Mehul Bhalala)
18/09/17在14:47
#10 楼
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);
}
}
}
评论
这有点令人迷惑
–穆斯塔法
19-10-11在11:54
#11 楼
@ 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,而我只知道在执行期间如何动态进行声明?
– natrayan
19年8月17日在1:23
#12 楼
就我而言,我已经使用上述技术加载了js
和css
visjs
文件-效果很好。我从ngOnInit()
调用新函数注意:我无法通过简单地在HTML模板文件中添加
<script>
和<link>
标记来加载该函数。 loadVisJsScript() {
console.log('Loading visjs js/css files...');
let script = document.createElement('script');
script.src = "../../assets/vis/vis.min.js";
script.type = 'text/javascript';
script.async = true;
script.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(script);
let link = document.createElement("link");
link.type = "stylesheet";
link.href = "../../assets/vis/vis.min.css";
document.getElementsByTagName('head')[0].appendChild(link);
}
评论
我想从外部网络源加载CSS文件。当前,我遇到错误消息“不允许加载本地资源”。.建议。
–卡兰
18年6月19日在6:29
我尝试过这种方式。尽管它对于脚本文件运行良好,但不适用于样式表
– 118218
19年2月6日,11:22
#13 楼
@ d123546我遇到了同样的问题,现在可以在组件中使用ngAfterContentInit(生命周期挂钩)使其工作:
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));
}
#14 楼
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();
}
});
}
}
#15 楼
样本可以是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();
});
评论
node.js中ES6变量导入名称可能重复吗?