简体   繁体   English

从 Angular 组件动态加载外部 javascript 文件

[英]Dynamically load external javascript file from Angular component

I am creating an Angular application using Angular 4 and the CLI.我正在使用 Angular 4 和 CLI 创建一个 Angular 应用程序。 I am trying to add the SkyScanner search widget into one of my components.我正在尝试将 SkyScanner 搜索小部件添加到我的组件之一中。

Skyscanner Widget Example Skyscanner 小部件示例

Part of the implementation requires the addition of a new external script:部分实现需要添加一个新的外部脚本:

<script src="https://widgets.skyscanner.net/widget-server/js/loader.js" async></script>

I am not sure of the correct way to reference this file.我不确定引用此文件的正确方法。 If I add the script into my index.html file, the widget doesn't load unless a full page refresh is performed.如果我将脚本添加到我的 index.html 文件中,则除非执行完整页面刷新,否则不会加载小部件。 I assume the script tries to manipulate the DOM on load and the elements don't exist when the script runs.我假设脚本尝试在加载时操作 DOM,并且脚本运行时元素不存在。

What is the correct way to load the script only when the component containing the Skyscanner widget is loaded?仅在加载包含 Skyscanner 小部件的组件时加载脚本的正确方法是什么?

Try to load external JavaScript on component load as below :尝试在组件加载时加载外部 JavaScript,如下所示:

loadAPI: Promise<any>;

constructor() {        
    this.loadAPI = new Promise((resolve) => {
        this.loadScript();
        resolve(true);
    });
}

public loadScript() {        
    var isFound = false;
    var scripts = document.getElementsByTagName("script")
    for (var i = 0; i < scripts.length; ++i) {
        if (scripts[i].getAttribute('src') != null && scripts[i].getAttribute('src').includes("loader")) {
            isFound = true;
        }
    }

    if (!isFound) {
        var dynamicScripts = ["https://widgets.skyscanner.net/widget-server/js/loader.js"];

        for (var i = 0; i < dynamicScripts.length; i++) {
            let 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);
        }

    }
}

I had the same problem, but in my case, I was importing 10 libraries at the end of the html file, and these libraries have a lot of methods, listeners, events, and more, and in my case I didn't need to call a method specifically.我遇到了同样的问题,但就我而言,我在 html 文件的末尾导入了 10 个库,这些库有很多方法、侦听器、事件等,在我的情况下,我不需要专门调用一个方法。

The example about what I had:关于我所拥有的示例:

<!-- app.component.html -->

<div> 
 ...
</div>

<script src="http://www.some-library.com/library.js">
<script src="../assets/js/my-library.js"> <!-- a route in my angular project -->

As mentioned, it didn't work.如前所述,它没有用。 Then, I find somehing that helped me: Milad response然后,我找到了一些对我有帮助的东西: Milad 回应

  1. Remove the script calls in the app.component.html.删除 app.component.html 中的脚本调用。 You have to link these scripts in the app.component.ts file.您必须在 app.component.ts 文件中链接这些脚本。

  2. In ngOnInit(), use a method to append the libraries, for example:在 ngOnInit() 中,使用一个方法来附加库,例如:

`` ``

<!-- app.component.ts -->

export class AppComponent implements OnInit {
   title = 'app';
   ngOnInit() {
     this.loadScript('http://www.some-library.com/library.js');
     this.loadScript('../assets/js/my-library.js');
   }
  }

  public loadScript(url: string) {
    const body = <HTMLDivElement> document.body;
    const script = document.createElement('script');
    script.innerHTML = '';
    script.src = url;
    script.async = false;
    script.defer = true;
    body.appendChild(script);
  }
}

It functions for me.它对我有用。 I use Angular 6, hope it helps.我使用 Angular 6,希望它有所帮助。

I have done this code snippet我已经完成了这个代码片段

 addJsToElement(src: string): HTMLScriptElement {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = src;
    this.elementRef.nativeElement.appendChild(script);
    return script;
  }

And then call it like this然后这样称呼

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

EDIT: With new renderer Api it can be written like this编辑:使用新的渲染器 Api 可以这样编写

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

StackBlitz闪电战

add loader.js to your assets folder then in your angular-cli.jsonloader.js添加到您的资产文件夹,然后添加到您的angular-cli.json

"scripts": ["./src/assets/loader.js",]

then add this to your typings.d.ts然后将此添加到您的typings.d.ts

 declare var skyscanner:any;

and you will be able to use it你将能够使用它

  skyscanner.load("snippets","2");

You can create your own directive to load script as below您可以创建自己的指令来加载脚本,如下所示

import { Directive, OnInit, Input } from '@angular/core';

@Directive({
    selector: '[appLoadScript]'
})
export class LoadScriptDirective implements OnInit{

    @Input('script') param:  any;

    ngOnInit() {
        let node = document.createElement('script');
        node.src = this.param;
        node.type = 'text/javascript';
        node.async = false;
        node.charset = 'utf-8';
        document.getElementsByTagName('head')[0].appendChild(node);
    }

}

And you can use it anywhere in your components template as below您可以在组件模板中的任何位置使用它,如下所示

<i appLoadScript  [script]="'script_file_path'"></i>

For example, to dynamically load JQuery in your component, insert below code in your component's template例如,要在组件中动态加载 JQuery,请在组件模板中插入以下代码

<i appLoadScript  [script]="'/assets/baker/js/jquery.min.js'"></i>

You can do one thing你可以做一件事

if you have angular-cli.json如果你有 angular-cli.json

then you can declare to script然后你可以声明脚本

like喜欢

"scripts": ["../src/assets/js/loader.js"]

And then declare skyscanner in your component然后在你的组件中声明 skyscanner

like喜欢

declare var skyscanner:any;

Thats it!就是这样!

Hope this help you希望这对你有帮助

Note: This is specially for external js links!注意:这是专门针对外部js链接的! Step 1. Add your angular script into the index.html file at the bottom of your body is recommended!步骤 1. 建议将您的 angular 脚本添加到正文底部的 index.html 文件中! I tried all other ways but failed.我尝试了所有其他方法但失败了。

 <!-- File Name: index.html and its inside src dir--> <body class=""> <app-root></app-root> <!-- Icons --> <script src="https://unpkg.com/feather-icons/dist/feather.min.js"></script> </body>

Next there are two ways to do this... In Anguar5 use inside your component folder at the top type in this code接下来有两种方法可以做到这一点...在 Anguar5 中,在此代码的顶部类型的组件文件夹中使用

declare var feather:any;

and then inside your class call the method you need.然后在您的类中调用您需要的方法。 For example例如

 //FileName: dashboard.component.ts import { Component, OnInit } from '@angular/core'; declare var feather:any; export class DashboardComponent implements OnInit{ ngOnInit(){ feather.replace(); } }

and this should run your code!这应该运行你的代码! The other way which might work in older version.另一种可能适用于旧版本的方式。 I have not checked!我没查过!

 //FileName: dashboard.component.ts import { Component, OnInit } from '@angular/core'; export class DashboardComponent implements OnInit{ ngOnInit(){ let node = document.createElement('script'); node.innerText='feather.replace()'; node.type = 'text/javascript'; node.async = false; node.charset = 'utf-8'; document.getElementsByTagName('body')[0].appendChild(node); } }

If you are not getting my code then also give try to this link too.如果你没有得到我的代码,那么也试试这个链接

Hope this helps!希望这可以帮助!

Little late but i prefer doing this way (service way)....有点晚了,但我更喜欢这样做(服务方式)...

import { Injectable } from '@angular/core';
import { Observable } from "rxjs";

interface Scripts {
  name: string;
  src: string;
}

export const ScriptStore: Scripts[] = [
  { name: 'script-a', src: 'assets/js/a.js' },
  { name: 'script-b', src: 'assets/js/b.js' },
  { name: 'script-c', src: 'assets/js/c.js' }
];

declare var document: any;

@Injectable()
export class FileInjectorService {

  private scripts: any = {};

  constructor() {
    ScriptStore.forEach((script: any) => {
      this.scripts[script.name] = {
        loaded: false,
        src: script.src
      };
    });
  }

  loadJS(...scripts: string[]) {
    const promises: any[] = [];
    scripts.forEach((script) => promises.push(this.loadJSFile(script)));
    return Promise.all(promises);
  }

  loadJSFile(name: string) {
    return new Promise((resolve, reject) => {
      if (!this.scripts[name].loaded) {
        let script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = this.scripts[name].src;
        if (script.readyState) {
            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 {
            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);
      } else {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      }
    });
  }

}

Then in my component i could do something like:然后在我的组件中,我可以执行以下操作:

ngOnInit() {
  this.fileInjectorService.loadJS('script-a', 'script-c').then(data => {
    // Loaded A and C....
  }).catch(error => console.log(error));
}

Tested in Angular 6/7在 Angular 6/7 中测试

The accepted answer is correct but won't work because browser takes little more time to parse the script after downloading it.接受的答案是正确的,但不起作用,因为浏览器在下载脚本后花费的时间很少。 So if any variable is being used from the loaded script, then it needs to be used on onload event of the newly created html script element.因此,如果加载的脚本中使用了任何变量,则需要在新创建的 html 脚本元素的 onload 事件上使用它。 I have improved the accepted answer as mentioned below -我已经改进了下面提到的接受的答案 -

loadAPI: Promise<any>;

constructor() {
    this.loadAPI = new Promise((resolve) => {
        let node = this.loadScript();
        if (node) {
            node.onload = () => {
                resolve(true);
            };
        } else {
            resolve(true);
        }
    });
}

ngOnInit() {
    this.loadAPI
        .then((flag) => {
        //Do something when script is loaded and parsed by browser
    });
}

loadScript() {
    let node = undefined;
    let isFound = false;
    const scripts = document.getElementsByTagName('script')
    for (let i = 0; i < scripts.length; ++i) {
        // Check if script is already there in html
        if (scripts[i].getAttribute('src') != null && scripts[i].getAttribute('src').includes("loader")) {
          isFound = true;
        }
    }

    if (!isFound) {
        const dynamicScript = 'https://widgets.skyscanner.net/widget-server/js/loader.js';
        node = document.createElement('script');
        node.src = dynamicScript;
        node.type = 'text/javascript';
        node.async = false;
        node.charset = 'utf-8';
        document.getElementsByTagName('head')[0].appendChild(node);
        return node;
    }
    return node;
}

This is working for me after so many code trials经过如此多的代码试验后,这对我有用

ngOnInit() {
    this.loadFormAssets().then(() => {console.log("Script Loaded");}).catch(() => {console.log("Script Problem");});
  }

 public loadFormAssets() {
    return new Promise(resolve => {

      const scriptElement = document.createElement('script');
      scriptElement.src =this.urls.todojs;
      scriptElement.onload = resolve;
      document.body.appendChild(scriptElement);

      const scriptElement1 = document.createElement('script');
      scriptElement1.src =this.urls.vendorjs;
      scriptElement1.onload = resolve;
      document.body.appendChild(scriptElement1);

    });
  }

In my case I had to load a few different files that depend on one another (some sort of something that uses bootstrap that then uses a jquery plugin that then uses jquery) and they all init immediately on loading, assuming they are loaded in sync on a web page.在我的情况下,我不得不加载一些相互依赖的不同文件(某种使用引导程序然后使用 jquery 插件然后使用 jquery 的东西)并且它们都在加载时立即初始化,假设它们是同步加载的一个网页。 All the other answers assume that you are loading files that are completely unrelated (or wait for you to init after everything gets loaded) - and with my setup this would throw all kinds of missing variable issues.所有其他答案都假设您正在加载完全不相关的文件(或者在加载所有内容后等待您进行初始化)-并且在我的设置中,这会引发各种缺失的变量问题。

My solution was to create a Promise chain (not a Promise list like @carlitoxenlaweb did - which would resolve everything in parallel), so that the next file only gets loaded after the previous file has finished init:我的解决方案是创建一个Promise链(而不是像 @carlitoxenlaweb 那样的Promise列表 - 这将并行解决所有问题),以便仅在前一个文件完成初始化后加载下一个文件:

    private myScripts = [
        '/assets/js/jquery-2.2.4.min.js',
        '/assets/js/bootstrap.min.js',
        '/assets/js/jquery.bootstrap.js',
        '/assets/js/jquery.validate.min.js',
        '/assets/js/somescript.js',
    ];
    private loadScripts() {
        let container:HTMLElement = this._el.nativeElement;
        let promise = Promise.resolve();
        for (let url of this.myScripts) {
            promise = promise.then(_ => new Promise((resolve, reject) => {
                let script = document.createElement('script');
                script.innerHTML = '';
                script.src = url;
                script.async = true;
                script.defer = false;
                script.onload = () => { resolve(); }
                script.onerror = (e) => { reject(e); }
                container.appendChild(script);
            }));
        }
    }

Since the source URL allows you to invoke a global function, you can use that to setup a custom event handler.由于源 URL 允许您调用全局函数,因此您可以使用它来设置自定义事件处理程序。

index.html索引.html

<script 
  type="text/javascript"
  src="http://www.bing.com/api/maps/mapcontrol?callback=onBingLoaded&branch=release"
  async defer
></script>
<script>
  function onBingLoaded() {
    const event = new CustomEvent("bingLoaded");
    window.dispatchEvent(event);
  }
</script>

Now that we've dispatched our custom event to the window object, we can listen for it using the decorator @HostListener Angular provides in our component.现在我们已经将自定义事件分派到 window 对象,我们可以使用我们组件中提供的装饰器@HostListener Angular 来监听它。

app.component.ts app.component.ts

export class AppComponent {
  @ViewChild('mapCanvas')
  mapCanvas!: ElementRef;
  private map!: Microsoft.Maps.Map;

  @HostListener('window:bingLoaded', ['$event'])
  defineMapCanvas() {
    this.map = new Microsoft.Maps.Map(
      this.mapCanvas.nativeElement,
      {
        credentials: [YOUR API KEY HERE],
        ...other options
      }
    );
  }

Reference: https://angular.io/api/core/HostListener参考: https : //angular.io/api/core/HostListener

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM