I'm building a web application using angular 2, typescript and YouTube API to add a player to the page after the user login.
So once logged in, the app loads the following component:
export class MyComponent implements OnInit {
myService: MyService;
constructor( private _myService: MyService ) {
this.myService = _myService;
}
ngOnInit() {
this._myService.loadAPI();
}
}
The component html contains the following tag:
<iframe id="player" type="text/html" width="640" height="360"
src="http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1"
frameborder="0" allowfullscreen></iframe>
And finally, the service has the following:
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);
}
I've read that the function "onYouTubeIframeAPIReady" is automatically called by the API, so I wonder what should I do differently to have it working properly.
You need to define your function,
onYouTubeIframeAPIReady
, on the global object. This works exactly the same way as in the linked answer for JavaScript. What follows is all 100% JavaScript stuff here, applicable to TypeScript by way of its superset of JavaScript nature.If you are using modules, as is generally the case with an Angular 2 application, then your code is isolated and does not execute in the global scope by default. This means that in order to define a global, we need to obtain a reference to the Global Object. In a browser this is very simple as
window
refers to the global (unless it is shadowed).What you need to write is quite straightforward. It is essentially
That means taking your current code, which looks like this
And changing it to this
You will get a TypeScript error telling you that
window
has no definition foronYouTubeIframeAPIReady
. This is easily resolved in numerous ways, but I just will illustrate two possibilities, either will do the job, and technically neither is necessary since TypeScript will still emit code in spite of the error.Specify a type assertion on window that suppresses the error
Declare the member on window so that you can assign to it without an error. Inside a module (recall we are not in the global scope) we can use the following form
Remember all JavaScript is valid TypeScript and that TypeScript does not add behavior or functionality to JavaScript. It is a typed view, an interpretation if you will, of JavaScript that allows it to be statically verified and have excellent tooling, to catch errors, provide a productive editing experience, and allow expectations to be documented at code level.
This is just JavaScript. It is the very same solution as used in Youtube iframe api not triggering onYouTubeIframeAPIReady, I only posted it because there seemed to be a disconnect.
Addendum: It is worth noting that if you using a module loader such as SystemJS or RequireJS, you can abstract the manual script tag injection process via loader configuration. The benefit is cleaner, more declarative code and also increased testability as you can then stub the YouTube dependency, isolating your tests from the network.
For SystemJS, you would use the following configuration
You can write
Now if you wanted to test this code, mocking the YouTube API, you could write
test/test-stubs/stub-youtube-api.ts
test/services/youtube-service.spec.ts