javascript - Wie lädt man externe Skripte dynamisch in Angular?
Ich habe dieses Modul, das die externe Bibliothek zusammen mit zusätzlicher Logik komponentisiert, ohne das <script>-Tag direkt in die index.html einzufügen:
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 {...}
Ich bemerke, dass der import gemäß der ES6-Spezifikation statisch ist und während der TypeScript-Transpilierung statt zur Laufzeit aufgelöst wird.
Gibt es eine Möglichkeit, es so konfigurierbar zu machen, dass die Datei file.js entweder von einem CDN oder aus einem lokalen Ordner geladen wird? Wie kann man Angular 2 sagen, ein Skript dynamisch zu laden?
Antwort #1
Sie können die folgende Technik verwenden, um JS-Skripte und Bibliotheken in Ihrem Angular-Projekt bei Bedarf dynamisch zu laden.
script.store.ts wird den Pfad des Skripts entweder lokal oder auf einem entfernten Server enthalten und einen Namen, der verwendet wird, um das Skript dynamisch zu laden
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 ist ein injizierbarer Dienst, der das Laden von Skripten übernimmt, kopiere script.service.ts wie es ist
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);
}
});
}
}
Fügen Sie diesen ScriptService überall dort ein, wo Sie ihn benötigen, und laden Sie JS-Bibliotheken wie folgt
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
Antwort #2
Wenn Sie system.js verwenden, können Sie System.import() zur Laufzeit verwenden:
export class MyAppComponent {
constructor(){
System.import('path/to/your/module').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}
Wenn Sie webpack verwenden, können Sie die umfangreiche Unterstützung für Code-Splitting mit require.ensure voll ausnutzen:
export class MyAppComponent {
constructor() {
require.ensure(['path/to/your/module'], require => {
let yourModule = require('path/to/your/module');
yourModule.someFunction();
});
}
}
Antwort #3
Das könnte funktionieren. Dieser Code fügt beim Klicken auf die Schaltfläche dynamisch das <script>-Tag zum head der HTML-Datei hinzu.
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);
}
}
Antwort #4
Eine weitere Möglichkeit wäre es, dafür das scriptjs-Paket zu verwenden, das
ermöglicht es Ihnen, Skriptressourcen bei Bedarf von jeder URL zu laden
Beispiel
Installieren Sie das Paket:
npm i scriptjs
und Typdefinitionen für scriptjs:
npm install --save @types/scriptjs
Dann die $script.get()-Methode importieren:
import { get } from 'scriptjs';
und schließlich die Skriptressource laden, in unserem Fall die Google Maps-Bibliothek:
export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}
Demo
Antwort #5
Ich habe die Antwort von @rahul kumar geändert, sodass sie stattdessen Observables verwendet:
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
}
Antwort #6
Sie können mehrere Skripte dynamisch so in Ihrer component.ts-Datei laden:
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);
}
}
und rufen Sie diese Methode im Konstruktor auf,
constructor() {
this.loadScripts();
}
Hinweis: Um weitere Skripte dynamisch zu laden, fügen Sie sie dem dynamicScripts-Array hinzu.
Antwort #7
Hi, du kannst Renderer2 und ElementRef mit nur ein paar Zeilen Code verwenden:
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);
}
Die onload-Funktion kann verwendet werden, um die Skriptfunktionen aufzurufen, nachdem das Skript geladen wurde. Dies ist sehr nützlich, wenn man die Aufrufe in der ngOnInit() durchführen muss.
Antwort #8
Ich habe dieses Code-Snippet mit der neuen Renderer-API gemacht
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;
}
Und dann nenn es so
this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
console.log('SkyScanner Tag loaded');
}
StackBlitz
