Come caricare gli script esterni in modo dinamico in Angolare?

Ho questo modulo che componente la libreria esterna insieme alla logica aggiuntiva senza aggiungere il tag direttamente nel index.html:

 import 'http://external.com/path/file.js' //import '../js/file.js' @Component({ selector: 'my-app', template: `  
Template
` }) export class MyAppComponent {...}

Ho notato che l’ import specifiche ES6 è statica e risolta durante il transpiling TypeScript piuttosto che in fase di runtime.

In ogni caso per renderlo configurabile in modo che il file.js verrà caricato da CDN o dalla cartella locale? Come dire ad Angular 2 di caricare uno script in modo dinamico?

Se stai usando system.js, puoi usare System.import() in fase di runtime:

 export class MyAppComponent { constructor(){ System.import('path/to/your/module').then(refToLoadedModule => { refToLoadedModule.someFunction(); } ); } 

Se stai usando il webpack, puoi sfruttare appieno il suo robusto supporto per la suddivisione del codice con require.ensure :

 export class MyAppComponent { constructor() { require.ensure(['path/to/your/module'], require => { let yourModule = require('path/to/your/module'); yourModule.someFunction(); }); } } 

È ansible utilizzare la seguente tecnica per caricare dynamicmente gli script JS e le librerie su richiesta nel progetto Angular.

script.store.ts conterrà il percorso dello script localmente o su un server remoto e un nome che verrà utilizzato per caricare lo script in modo dinamico

  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 è un servizio iniettabile che gestirà il caricamento dello script, copierà script.service.ts così com’è

 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); } }); } } 

Inietti questo ScriptService ovunque ti serva e carica libs js come questo

 this.script.load('filepicker', 'rangeSlider').then(data => { console.log('script loaded ', data); }).catch(error => console.log(error)); 

Questo potrebbe funzionare. Questo codice aggiunge dynamicmente il tag all'inizio del file html sul pulsante su cui si fa clic.

 const url = 'http://iknow.com/this/does/not/work/either/file.js'; export class MyAppComponent { loadAPI: Promise; 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); } } 

Ho modificato la risposta di @rahul kumars, in modo che usi Observables invece:

 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 { return new Observable((observer: Observer) => { 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 } 

Puoi caricare più script in modo dinamico come questo nel tuo file 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); } } 

e chiama questo metodo all'interno del costruttore,

 constructor() { this.loadScripts(); } 

Nota: per caricare altri script in modo dinamico, aggiungili alla matrice dynamicScripts .

Nel mio caso, ho caricato entrambi i file visjs js e css utilizzando la tecnica di cui sopra – che funziona alla grande. Chiamo la nuova funzione da ngOnInit()

Nota: non ho potuto farlo caricare semplicemente aggiungendo un e al file modello 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); } 

Ho eseguito questo snippet di codice con la nuova API di rendering

  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; } 

E poi chiamalo così

 this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => { console.log('SkyScanner Tag loaded'); } 

StackBlitz

@ d123546 Ho affrontato lo stesso problema e ho funzionato ora usando ngAfterContentInit (Lifecycle Hook) nel componente in questo modo:

 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)); } 

La soluzione di @ rahul-kumar funziona bene per me, ma volevo chiamare la mia funzione javascript nel mio typescript

 foo.myFunctions() // works in browser console, but foo can't be used in typescript file 

L’ho risolto dichiarandolo nel mio typescript:

 import { Component } from '@angular/core'; import { ScriptService } from './script.service'; declare var foo; 

E ora, posso chiamare foo ovunque nel mio file di tipografia

un campione può essere

script-loader.service.ts file

 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} */ 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} */ 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} */ 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 = $(''). 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}); } }); } } 

e l’uso

prima iniezione

  constructor( private _script: ScriptLoaderService) { } 

poi

 ngAfterViewInit() { this._script.loadScripts('app-wizard-wizard-3', ['assets/demo/default/custom/crud/wizard/wizard.js']); } 

o

  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(); }); 

Ho un buon modo per caricare in modo dinamico gli script! Ora uso ng6, echarts4 (> 700Kb), ngx-echarts3 nel mio progetto. quando li uso dai documenti di ngx-echarts, ho bisogno di import echarts in angular.json: “scripts”: [“./ node_modules / echarts / dist / echarts.min.js”] quindi nel modulo di login, pagina durante il caricamento degli script .js, questo è un file grande! Io non lo voglio

Quindi, penso che carichi angolari ogni modulo come un file, posso inserire un router resolver per precaricare js, quindi iniziare il caricamento del modulo!

// PreloadScriptResolver.service.js

 /**动态加载js的服务 */ @Injectable({ providedIn: 'root' }) export class PreloadScriptResolver implements Resolve { // 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 { 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 { return this.load(...route.routeConfig.data.preloadScripts); } } 

Quindi, nel submodule-routing.module.ts, importa questo PreloadScriptResolver:

 const routes: Routes = [ { path: "", component: DashboardComponent, canActivate: [AuthGuardService], canActivateChild: [AuthGuardService], resolve: { preloadScripts: PreloadScriptResolver }, data: { preloadScripts: ["echarts"] // important! }, children: [.....] } 

Questo codice funziona bene e promette che: Dopo il caricamento del file js, il modulo inizia il caricamento! questo Resolver può essere utilizzato su molti router