Как динамически загружать внешние скрипты в угловой?
у меня есть этот модуль, который компонентным подходом внешние библиотеки вместе с дополнительной логики без добавления <script> тег непосредственно в индекс.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, чтобы загрузить скрипт динамически?
11 ответов:
если вы используете систему.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(); }); } }
вы можете использовать следующий метод, чтобы динамически загружайте скрипты и библиотеки 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как это это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));
Это может сработать. Этот код динамически добавляет
<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); } }
Я изменил @ rahul kumars ответ, так что он использует наблюдаемые вместо этого:
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 }
вы можете загрузить несколько скриптов динамически такое
component.tsfile: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массив.
в моем случае, я загрузил как
jsиcssvisjsфайлы с использованием вышеуказанной техники-которая отлично работает. Я вызываю новую функцию из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); }
Я сделал этот фрагмент кода с новым 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'); }
@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)); }
решение@rahul-kumar работает хорошо для меня, но я хотел вызвать свою функцию javascript в моем typescript
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;а теперь, я могу позвонить фу в любом месте моего файла typecript
пример может быть
скрипт-загрузчик.услуга.файл 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(); });
у меня есть хороший способ динамической загрузки скриптов! Сейчас я использую ng6, echarts4 (>700кб ) ,фабриката необходмо предусмотреть-echarts3 в моем проекте. когда я использую их в документах ngx-echarts, мне нужно импортировать echarts в angular.формат JSON : "файлы сценариев."[:"/node_modules/echarts/dist / echarts.минута.js"] таким образом, в модуле входа в систему, страница при загрузке скриптов.джей, это большой файл! Я не хочу этого.
Итак, я думаю, что угловые нагрузки каждого модуля в виде файла, я могу вставить решатель маршрутизатора для предварительной загрузки js, а затем начать модуль погрузка!
/ / PreloadScriptResolver.услуга.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); } }затем в подмодуле-маршрутизация.модуль.ts ,импорт этого PreloadScriptResolver:
const routes: Routes = [ { path: "", component: DashboardComponent, canActivate: [AuthGuardService], canActivateChild: [AuthGuardService], resolve: { preloadScripts: PreloadScriptResolver }, data: { preloadScripts: ["echarts"] // important! }, children: [.....] }этот код работает хорошо, и его обещает, что: после того, как JS файл загружен, то модуль начать загрузку! этот решатель можно использовать во многих маршрутизаторах
Comments