Динамически загружать внешний файл javascript из компонента Angular

Я создаю приложение Angular, используя Angular 4 и CLI. Я пытаюсь добавить виджет поиска SkyScanner в один из моих компонентов.

Пример виджета Skyscanner

Часть реализации требует добавления нового внешнего script:

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

Я не уверен в правильности ссылки на этот файл. Если я добавлю script в мой файл index.html, виджет не загружается, если не выполняется полное обновление страницы. Я предполагаю, что script пытается манипулировать DOM при загрузке, а элементы не существуют, когда выполняется script.

Каков правильный способ загрузки script только при загрузке компонента, содержащего виджет Skyscanner?

Ответы

Ответ 1

Попробуйте загрузить внешний 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);
        }

    }
}

Ответ 2

У меня была та же проблема, но в моем случае я импортировал 10 библиотек в конце файла HTML, и у этих библиотек было много методов, слушателей, событий и т.д., И в моем случае мне не нужно было вызовите метод специально.

Пример о том, что у меня было:

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

Как уже упоминалось, это не сработало. Затем я нахожу то, что мне помогло: ответ Милад

  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, надеюсь, это поможет.

Ответ 3

Я сделал этот фрагмент кода

 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

Ответ 4

добавьте loader.js в папку с вашими активами, а затем в angular-cli.json

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

затем добавьте это в свой typings.d.ts

 declare var skyscanner:any;

и вы сможете использовать его

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

Ответ 5

Вы можете создать свою собственную директиву для загрузки script, как показано ниже

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>

Ответ 6

Вы можете сделать одну вещь

если у вас есть angular -cli.json

то вы можете объявить script

как

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

И затем объявите skyscanner в своем компоненте

like

declare var skyscanner:any;

Вот оно!

Надеюсь, что эта помощь вам поможет.

Ответ 7

Примечание: это специально для внешних ссылок js! Шаг 1. Рекомендуем добавить свой угловой скрипт в файл 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>

Ответ 8

Немного поздно, но я предпочитаю делать так (сервисный способ)....

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

Ответ 9

Принятый ответ правильный, но не сработает, потому что браузеру требуется немного больше времени для анализа скрипта после его загрузки. Таким образом, если какая-либо переменная используется из загруженного скрипта, то ее необходимо использовать для события onload недавно созданного элемента html script. Я улучшил принятый ответ, как указано ниже -

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