Как динамически загружать внешние скрипты в 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 {...}
Я замечаю, что import
по спецификации ES6 статичен и разрешен во время TypeScript трансляции, а не во время выполнения.
В любом случае, чтобы настроить его, чтобы файл .js загружался либо из CDN, либо из локальной папки?
Как сообщить Angular 2 динамически загружать script?
Ответы
Ответ 1
Если вы используете system.js, вы можете использовать System.import()
во время выполнения:
export class MyAppComponent {
constructor(){
System.import('path/to/your/module').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}
Если вы используете веб-пакет, вы можете в полной мере воспользоваться его надежной поддержкой разделения кода с помощью require.ensure
:
export class MyAppComponent {
constructor() {
require.ensure(['path/to/your/module'], require => {
let yourModule = require('path/to/your/module');
yourModule.someFunction();
});
}
}
Ответ 2
Вы можете использовать следующую технику для динамической загрузки JS-скриптов и библиотек по запросу в вашем проекте Angular.
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 libs, как это
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
Ответ 3
Это может сработать. Этот код динамически добавляет тег <script>
к head
файла html при нажатии кнопки.
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);
}
}
Ответ 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
}
Ответ 5
Вы можете загружать несколько сценариев динамически, как это, в файле 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
.
Ответ 6
Еще один вариант будет использовать пакет 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...
});
}
}
демонстрация
Ответ 7
У меня есть хороший способ динамически загружать скрипты! Теперь я использую ng6, echarts4 (> 700Kb), ngx-echarts3 в моем проекте. когда я использую их по документам ngx-echarts, мне нужно импортировать eharts в angular.json: "scripts": ["./node_modules/echarts/dist/echarts.min.js"], таким образом, в модуле входа, страница при загрузке скриптов.js, это большой файл! Я не хочу этого.
Итак, я думаю, что угловые нагрузки каждого модуля в виде файла, я могу вставить маршрутизатор-резольвер для предварительной загрузки 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);
}
}
Затем в subodule-routing.module.ts импортируйте этот PreloadScriptResolver:
const routes: Routes = [
{
path: "",
component: DashboardComponent,
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
resolve: {
preloadScripts: PreloadScriptResolver
},
data: {
preloadScripts: ["echarts"] // important!
},
children: [.....]
}
Этот код работает хорошо, и его обещания: После загрузки файла js, модуль начнет загрузку! этот Resolver может использоваться во многих маршрутизаторах
Ответ 8
Решение @rahul-kumar работает хорошо для меня, но я хотел назвать свою функцию javascript в своем typescript
foo.myFunctions() // works in browser console, but foo can't be used in typescript file
Я исправил его, объявив его в моем typescript:
import { Component } from '@angular/core';
import { ScriptService } from './script.service';
declare var foo;
И теперь я могу называть foo где-нибудь в файле typecript
Ответ 9
В моем случае я загрузил файлы js
и css
visjs
используя вышеприведенную технику, которая отлично работает. Я вызываю новую функцию из ngOnInit()
Примечание. Я не мог загрузить его, просто добавив <script>
и <link>
в файл шаблона html.
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);
}
Ответ 10
Я сделал этот фрагмент кода с помощью нового рендеринга 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
Ответ 11
@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));
}
Ответ 12
образец может быть
Файл 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();
});
Ответ 13
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();
}
});
}
}
Ответ 14
Угловое универсальное решение; Мне нужно было дождаться появления определенного элемента на странице, прежде чем загружать скрипт для воспроизведения видео.
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);
}
}
}
Ответ 15
У меня есть библиотека js в папке активов, где у меня есть несколько файлов javascript, которые позволяют создавать слайдер. У меня есть эти файлы в индексе углового проекта, который заставляет слайдер работать хорошо. Бывает, что когда я перенаправляю ползунок, он перестает работать. В соответствии с лей я должен загрузить файлы javascript из компонента в части ngOnInit, но я не могу сделать это funicone
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-inicio',
templateUrl: './inicio.component.html',
styleUrls: ['./inicio.component.css']
})
export class InicioComponent implements OnInit {
constructor() { }
ngOnInit() {
this.loadVisJsScript();
}
}
loadVisJsScript() {
console.log('Loading visjs js/css files...');
let script = document.createElement('script');
script.src = "../../../assets/plugins/OwlCarousel2-2.2.1/owl.carousel.js";
script.type = 'text/javascript';
script.async = true;
script.charset = 'utf-8';
document.getElementsByTagName('head')[0].appendChild(script);
}
не могу показать слайдер, который я хочу загрузить