Tìm kiếm trang web

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

Bài viết liên quan