onYouTubeIframeAPIReady 未在 angular2 网络应用程序上触发

onYouTubeIframeAPIReady not firing on angular2 web app

我正在使用 angular 2、typescript 和 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 自动调用,所以我想知道我应该做些什么不同的事情才能让它正常工作。

您需要在全局对象上定义函数 onYouTubeIframeAPIReady。这与 JavaScript 的链接答案中的工作方式完全相同。 接下来是所有 100% JavaScript 的内容,通过其 JavaScript 性质的超集适用于 TypeScript。

如果您正在使用模块,就像 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 上指定类型断言以抑制错误

    (window as any).onYouTubeIframeAPIReady = function () {}
    
  2. 在 window 上声明成员,这样您就可以无误地分配给它。在模块内部(回想一下我们不在全局范围内)我们可以使用以下形式

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

记住所有 JavaScript 都是有效的 TypeScript,并且 TypeScript 不会向 JavaScript 添加行为或功能。它是一个类型化的视图,如果你愿意,可以解释 JavaScript,它允许它被静态验证并具有优秀的工具,以捕获错误,提供高效的编辑体验,并允许在代码级别记录期望。

这只是 JavaScript。它与 Youtube iframe api not triggering onYouTubeIframeAPIReady 中使用的解决方案完全相同,我发布它只是因为似乎存在断开连接。


附录: 值得注意的是,如果您使用 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;
  }
}

现在,如果您想测试这段代码,模拟 YouTube API,您可以编写

test/test-stubs/stub-youtube-api.ts

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

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