onYouTubeIframeAPIR не запускается в веб-приложении angular2

Я создаю веб-приложение, используя angular 2, typcript и YouTube API, чтобы добавить проигрыватель на страницу после входа пользователя.

Так что после входа приложение загружает следующий компонент:

export class MyComponent implements OnInit {
  myService: MyService;

  constructor( private _myService: MyService ) {
    this.myService = _myService;
  }

  ngOnInit() {
   this._myService.loadAPI();
  }

}

HTML-компонент содержит следующий тег:

<iframe id="player" type="text/html" width="640" height="360"
      src="http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1"
      frameborder="0" allowfullscreen></iframe>

И, наконец, сервис имеет следующее:

  player: YT.Player;

  loadAPI(){
    var tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    console.log('API loaded'); // this is shown on the console.
  }

  onYouTubeIframeAPIReady(){
    this.player = new YT.Player('player', {
      events: {
        'onReady': this.onPlayerReady,
        'onStateChange': this.onPlayerStateChange
      }
    });
    console.log('youtube iframe api ready!'); // this is never triggered.
  }

  onPlayerReady(event){
    event.target.playVideo();
  }

  onPlayerStateChange(status){
    console.log(status.data);
  }  

Я читал, что функция "onYouTubeIframeAPIReady" автоматически вызывается API, поэтому мне интересно, что я должен делать по-другому, чтобы она работала правильно.

1 ответ

Решение

Вы должны определить свою функцию, onYouTubeIframeAPIReadyна глобальном объекте. Это работает точно так же, как в связанном ответе для JavaScript. Далее следует все на 100% JavaScript, применимый к TypeScript посредством его надмножества JavaScript-природы.

Если вы используете модули, как это обычно бывает с приложением Angular 2, то ваш код изолирован и по умолчанию не выполняется в глобальной области видимости. Это означает, что для определения глобала нам нужно получить ссылку на глобальный объект. В браузере это очень просто window относится к глобальному (если оно не затенено).

То, что вам нужно написать, довольно просто. Это по сути

window.onYouTubeIframeAPIReady = function () { ... };

Это означает, что вы берете ваш текущий код, который выглядит следующим образом

export default class YouTubeService {
  ...
  loadAPI() {
    var tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    console.log('API loaded'); // this is shown on the console.
  }

  onYouTubeIframeAPIReady() { }
}

И изменив это на это

export default class YouTubeService {
  ...
  loadAPI() {
    window.onYouTubeIframeAPIReady = function () { };

    var tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    console.log('API loaded'); // this is shown on the console.
  }
}

Вы получите ошибку TypeScript, сообщающую, что window не имеет определения для onYouTubeIframeAPIReady, Это легко решается многими способами, но я просто проиллюстрирую две возможности, либо они будут выполнять работу, и технически ни одна из них не нужна, поскольку TypeScript все равно будет генерировать код, несмотря на ошибку.

  1. Укажите утверждение типа в окне, которое подавляет ошибку

    (window as any).onYouTubeIframeAPIReady = function () {}
    
  2. Объявите элемент в окне, чтобы вы могли назначить его без ошибки. Внутри модуля (напомним, мы не находимся в глобальной области видимости) мы можем использовать следующую форму

    declare global {
      interface Window {
        onYouTubeIframeAPIReady?: () => void;
      }
    }
    

Помните, что весь JavaScript является допустимым TypeScript, и этот TypeScript не добавляет поведения или функциональности в JavaScript. Это типизированное представление, если хотите, интерпретация JavaScript, которая позволяет его статически проверять и иметь отличные инструменты, выявлять ошибки, обеспечивать продуктивный опыт редактирования и позволяет документировать ожидания на уровне кода.

Это просто JavaScript. Это то же самое решение, которое используется в Youtube, если iframe api не запускается на YouTubeIframe APIReady, я только опубликовал его, потому что казалось, что отключение.


Приложение: Стоит отметить, что если вы используете загрузчик модулей, такой как SystemJS или RequireJS, вы можете абстрагировать процесс ручного введения тега скрипта через конфигурацию загрузчика. Преимущество заключается в более чистом, более декларативном коде, а также в улучшении тестируемости, поскольку вы можете затем заглушить зависимость от YouTube, изолируя свои тесты от сети.

Для SystemJS вы должны использовать следующую конфигурацию

SystemJS.config({
  map: {
    "youtube": "https://www.youtube.com/iframe_api"
  },
  meta: {
    "https://www.youtube.com/iframe_api": {
      "format": "global",
      "scriptLoad": true,
      "build": false
    }
  }
});

Ты можешь написать

export default class YouTubeService {
  async loadAPI() {
    window.onYouTubeIframeAPIReady = function () {
      console.log('API loaded'); // this is shown on the console.
    };
    try {
      await import('youtube'); // automatically injects a script tag
    }
    catch (e) {
      console.error('The YouTube API failed to load');
    }
  }
}

declare global {
  interface Window {
    onYouTubeIframeAPIReady?: () => void;
  }
}

Теперь, если вы хотите протестировать этот код, высмеивая API YouTube, вы можете написать

тест / тест-заглушка / заглушки YouTube-api.ts

(function () {
  window.onYouTubeIframeAPIReady();
}());

тест / услуги / YouTube-service.spec.ts

import test from 'blue-tape';

import YouTubeService from 'src/services/youtube.service'

SystemJS.config({
  map: {
    "youtube": "test/test-stubs/stub-youtube-api.ts"
  }
});

if(typeof window === 'undefined') {
  global.window = {};
}


test('Service must define a callback for onYouTubeIframeAPIReady', async ({isNot}) => {
  const service = new YouTubeService();
  await service.loadAPI();
  t.isNot(undefined, window.onYouTubeIframeAPIReady);
});
Другие вопросы по тегам