pagina - javascript en angular 4
¿Cómo cargar scripts externos dinámicamente en Angular? (14)
¡Tengo una buena forma de cargar scripts dinámicamente! Ahora uso ng6, echarts4 (> 700Kb), ngx-echarts3 en mi proyecto. cuando los uso con los documentos de ngx-echarts, necesito importar echarts en angular.json: "scripts": ["./ node_modules / echarts / dist / echarts.min.js"], por lo tanto, en el módulo de inicio de sesión, página mientras se cargan los scripts .js, este es un archivo grande! No lo quiero
Entonces, creo que angular carga cada módulo como un archivo, puedo insertar un enrutador para precargar js, ¡y luego comenzar la carga del módulo!
// 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);
}
}
Luego, en submodule-routing.module.ts, importe este PreloadScriptResolver:
const routes: Routes = [
{
path: "",
component: DashboardComponent,
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
resolve: {
preloadScripts: PreloadScriptResolver
},
data: {
preloadScripts: ["echarts"] // important!
},
children: [.....]
}
Este código funciona bien y promete que: después de cargar el archivo js, ¡el módulo comenzará a cargar! este Resolver puede usar en muchos enrutadores
Tengo este módulo que compone la biblioteca externa junto con lógica adicional sin agregar la etiqueta
<script>
directamente en 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 {...}
Noté que la
import
mediante la especificación ES6 es estática y se resuelve durante la transcripción de TypeScript en lugar del tiempo de ejecución.
¿De todos modos para que sea configurable para que el archivo.js se cargue desde CDN o carpeta local? ¿Cómo decirle a Angular 2 que cargue un script dinámicamente?
@ d123546 Me enfrenté al mismo problema y lo puse a funcionar ahora usando ngAfterContentInit (Lifecycle Hook) en el componente de esta manera:
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));
}
En mi caso, he cargado los archivos
js
y
css
visjs
utilizando la técnica anterior, que funciona muy bien.
Llamo a la nueva función desde
ngOnInit()
Nota:
No pude hacer que se cargara simplemente agregando una etiqueta
<script>
y
<link>
al archivo de plantilla 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);
}
Esto podría funcionar.
Este código agrega dinámicamente la etiqueta
<script>
al
head
del archivo html al hacer clic en el botón.
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);
}
}
He hecho este fragmento de código con la nueva API de renderizador
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;
}
Y luego llámalo así
this.addJsToElement(''https://widgets.skyscanner.net/widget-server/js/loader.js'').onload = () => {
console.log(''SkyScanner Tag loaded'');
}
He modificado la respuesta de @rahul kumars, para que use Observables en su lugar:
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
}
La solución de @ rahul-kumar funciona bien para mí, pero quería llamar a mi función javascript en mi mecanografiado
foo.myFunctions() // works in browser console, but foo can''t be used in typescript file
Lo arreglé declarándolo en mi mecanografiado:
import { Component } from ''@angular/core'';
import { ScriptService } from ''./script.service'';
declare var foo;
Y ahora, puedo llamar a foo en cualquier parte de mi archivo de mecanografía
Otra opción más sería utilizar el
paquete
scriptjs
para el caso que
le permite cargar recursos de script a pedido desde cualquier URL
Ejemplo
Instala el paquete:
npm i scriptjs
y
definiciones de tipo para
scriptjs
:
npm install --save @types/scriptjs
Luego importe el
$script.get()
:
import { get } from ''scriptjs'';
y finalmente cargar el recurso de script, en nuestro caso la biblioteca de Google Maps:
export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}
Puede cargar
múltiples scripts dinámicamente
como este en su archivo
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);
}
}
y llama a este método dentro del constructor,
constructor() {
this.loadScripts();
}
Nota: Para que se carguen más scripts dinámicamente, agréguelos a la matriz
dynamicScripts
.
Puede usar la siguiente técnica para cargar dinámicamente scripts y bibliotecas JS a pedido en su proyecto Angular.
script.store.ts contendrá la ruta del script localmente o en un servidor remoto y un nombre que se usará para cargar el script dinámicamente
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
es un servicio inyectable que manejará la carga del script, copie
script.service.ts
tal como está
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);
}
});
}
}
Inyecte este
ScriptService
donde lo necesite y cargue js libs como este
this.script.load(''filepicker'', ''rangeSlider'').then(data => {
console.log(''script loaded '', data);
}).catch(error => console.log(error));
Si usa system.js, puede usar
System.import()
en tiempo de ejecución:
export class MyAppComponent {
constructor(){
System.import(''path/to/your/module'').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}
Si usa webpack, puede aprovechar al máximo su sólido soporte de división de código con
require.ensure
:
export class MyAppComponent {
constructor() {
require.ensure([''path/to/your/module''], require => {
let yourModule = require(''path/to/your/module'');
yourModule.someFunction();
});
}
}
Una solución universal angular; Necesitaba esperar a que un elemento en particular estuviera en la página antes de cargar un script para reproducir un video.
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);
}
}
}
una muestra puede ser
archivo 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});
}
});
}
}
y uso
primera inyección
constructor(
private _script: ScriptLoaderService) {
}
entonces
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();
});
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();
}
});
}
}