javascript - Làm thế nào để tải các tập lệnh bên ngoài một cách động trong Angular?
Tôi có một module này, nó phân tách các thành phần của thư viện bên ngoài cùng với logic bổ sung mà không thêm thẻ <script> trực tiếp vào 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 {...}
Tôi nhận thấy import theo đặc tả ES6 là tĩnh và được giải quyết trong quá trình biên dịch TypeScript thay vì tại thời điểm chạy.
Có cách nào để làm cho nó có thể cấu hình để file.js có thể tải từ CDN hoặc thư mục cục bộ không? Làm thế nào để bảo Angular 2 tải một script động?
Câu trả lời #1
Bạn có thể sử dụng kỹ thuật sau để tải động các tập lệnh JS và thư viện theo nhu cầu trong dự án Angular của bạn.
script.store.ts sẽ chứa đường dẫn của tập lệnh hoặc là trên máy local hoặc trên máy chủ từ xa và một tên sẽ được sử dụng để tải tập lệnh một cách động
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 là một dịch vụ có thể tiêm phụ thuộc, sẽ xử lý việc tải script, sao chép script.service.ts nguyên vẹn
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);
}
});
}
}
Tiêm ScriptService này vào bất cứ đâu bạn cần và tải các thư viện js như thế này
this.script.load('filepicker', 'rangeSlider').then(data => {
console.log('script loaded ', data);
}).catch(error => console.log(error));
Câu trả lời #2
Nếu bạn đang sử dụng system.js, bạn có thể dùng System.import() khi chạy:
export class MyAppComponent {
constructor(){
System.import('path/to/your/module').then(refToLoadedModule => {
refToLoadedModule.someFunction();
}
);
}
Nếu bạn đang sử dụng webpack, bạn có thể tận dụng tối đa hỗ trợ chia nhỏ mã mạnh mẽ của nó với require.ensure:
export class MyAppComponent {
constructor() {
require.ensure(['path/to/your/module'], require => {
let yourModule = require('path/to/your/module');
yourModule.someFunction();
});
}
}
Câu trả lời #3
Điều này có thể hoạt động. Mã này sẽ tự động thêm thẻ <script> vào phần head của tệp html khi nhấn nút.
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);
}
}
Câu trả lời #4
Một lựa chọn khác sẽ là sử dụng gói scriptjs cho vấn đề đó mà
cho phép bạn tải các tài nguyên kịch bản theo yêu cầu từ bất kỳ URL nào
Ví dụ
Cài đặt gói:
npm i scriptjs
và các định nghĩa kiểu cho scriptjs:
npm install --save @types/scriptjs
Sau đó nhập phương thức $script.get():
import { get } from 'scriptjs';
và cuối cùng tải tài nguyên kịch bản, trong trường hợp của chúng ta là thư viện Google Maps:
export class AppComponent implements OnInit {
ngOnInit() {
get("https://maps.googleapis.com/maps/api/js?key=", () => {
//Google Maps library has been loaded...
});
}
}
Bản trình diễn
Câu trả lời #5
Tôi đã chỉnh sửa câu trả lời của @rahul kumar, để nó sử dụng Observables thay vào đó:
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
}
Câu trả lời số 6
Bạn có thể tải nhiều tập lệnh động như thế này trong tệp component.ts của bạn:
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);
}
}
và gọi phương thức này bên trong hàm khởi tạo,
constructor() {
this.loadScripts();
}
Lưu ý: Để tải thêm các script một cách động, hãy thêm chúng vào mảng dynamicScripts.
Câu trả lời số 7
Chào bạn, bạn có thể sử dụng Renderer2 và elementRef chỉ với vài dòng mã:
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);
}
hàm onload có thể được sử dụng để gọi các hàm script sau khi script được tải, điều này rất hữu ích nếu bạn phải thực hiện các lời gọi trong ngOnInit()
Câu trả lời số 8
Tôi đã làm đoạn mã này với API renderer mới
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;
}
Và sau đó gọi nó như thế này
this.addJsToElement('https://widgets.skyscanner.net/widget-server/js/loader.js').onload = () => {
console.log('SkyScanner Tag loaded');
}
StackBlitz
