53

我正在使用 Angular 4 和 CLI 创建一个 Angular 应用程序。我正在尝试将 SkyScanner 搜索小部件添加到我的一个组件中。

Skyscanner 小部件示例

部分实现需要添加一个新的外部脚本:

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

我不确定引用此文件的正确方法。如果我将脚本添加到我的 index.html 文件中,则除非执行整页刷新,否则不会加载小部件。我假设脚本尝试在加载时操作 DOM,并且脚本运行时元素不存在。

只有在加载包含 Skyscanner 小部件的组件时才加载脚本的正确方法是什么?

4

12 回答 12

90

尝试在组件加载时加载外部 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);
        }

    }
}
于 2017-05-31T05:51:39.930 回答
47

我有同样的问题,但在我的情况下,我在 html 文件的末尾导入了 10 个库,这些库有很多方法、侦听器、事件等等,而在我的情况下,我不需要专门调用一个方法。

关于我所拥有的示例:

<!-- 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 -->

如前所述,它没有用。然后,我找到了一些对我有帮助的东西:Milad 回应

  1. 删除 app.component.html 中的脚本调用。您必须在 app.component.ts 文件中链接这些脚本。

  2. 在 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);
  }
}

它对我有用。我使用 Angular 6,希望它有帮助。

于 2018-06-04T06:28:06.723 回答
8

我已经完成了这个代码片段

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

然后像这样称呼它

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

编辑:使用新的渲染器 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

于 2017-05-31T16:15:19.690 回答
7

添加loader.js到您的资产文件夹,然后在您的angular-cli.json

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

然后将此添加到您的typings.d.ts

 declare var skyscanner:any;

你就可以使用它了

  skyscanner.load("snippets","2");
于 2017-05-26T15:01:43.543 回答
7

您可以创建自己的指令来加载脚本,如下所示

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

}

您可以在组件模板中的任何位置使用它,如下所示

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

例如,要在组件中动态加载 JQuery,请在组件模板中插入以下代码

<i appLoadScript  [script]="'/assets/baker/js/jquery.min.js'"></i>
于 2017-11-25T11:38:08.003 回答
2

接受的答案是正确的,但不起作用,因为浏览器在下载脚本后需要更多时间来解析脚本。因此,如果从加载的脚本中使用了任何变量,则需要在新创建的 html 脚本元素的 onload 事件上使用它。我已经改进了接受的答案,如下所述 -

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;
}
于 2019-05-30T09:08:27.250 回答
1

你可以做一件事

如果你有 angular-cli.json

然后你可以声明脚本

喜欢

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

然后在你的组件中声明 skyscanner

喜欢

declare var skyscanner:any;

而已!

希望这对你有帮助

于 2017-05-31T10:04:03.257 回答
1

注意:这是专门针对外部js链接的!步骤 1. 建议将 Angular 脚本添加到正文底部的 index.html 文件中!我尝试了所有其他方法,但都失败了。

<!-- 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>

接下来有两种方法可以做到这一点...在 Anguar5 中,在此代码顶部的组件文件夹中使用

declare var feather:any;

然后在你的类中调用你需要的方法。例如

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

这应该运行你的代码!另一种可能适用于旧版本的方式。我没查过!

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

}

如果你没有得到我的代码,那么也试试这个链接

希望这可以帮助!

于 2018-05-10T06:34:51.203 回答
1

有点晚了,但我更喜欢这样做(服务方式)....

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

}

然后在我的组件中,我可以执行以下操作:

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

在 Angular 6/7 中测试

于 2019-01-10T20:58:03.683 回答
0

经过这么多代码试验后,这对我有用

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

    });
  }
于 2020-01-30T05:36:08.583 回答
0

在我的情况下,我必须加载一些相互依赖的不同文件(某种使用引导程序的东西,然后使用 jquery 插件,然后使用 jquery)并且它们都在加载时立即初始化,假设它们是同步加载的一个网页。所有其他答案都假设您正在加载完全不相关的文件(或者在加载所有内容后等待您初始化) - 并且使用我的设置,这会引发各种丢失的变量问题。

我的解决方案是创建一个Promise链(不是Promise像@carlitoxenlaweb 那样的列表 - 这将并行解决所有问题),以便下一个文件仅在前一个文件完成初始化后加载:

    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);
            }));
        }
    }
于 2020-09-10T14:14:59.547 回答
0

由于源 URL 允许您调用全局函数,因此您可以使用它来设置自定义事件处理程序。

索引.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>

现在我们已经将自定义事件分派给了 window 对象,我们可以使用@HostListenerAngular 在我们的组件中提供的装饰器来监听它。

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

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

于 2021-07-19T15:11:22.377 回答