New to Angular2 here and wondering how to do this async request on array pattern (not so sure about the pattern name).
So let's say I use Angular2's Http
to get a YouTube Playlist that contains videoId
in each return item. Then I need to loop through this item with the videoId
and do another request to YouTube to get the individual video information. This can be another common HTTP request, get the list then loop through the list and get the details of every individual item.
let url = [YouTube API V3 list url]
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics';
this.http.get(url)
.map(res => res.json())
.subscribe(data => {
let newArray = data.items.map(entry => {
let videoUrl = detailUrl + '&id=' + entry.id.videoId +
'&key=' + this.googleToken;
this.http.get(videoUrl)
.map(videoRes => videoRes.json())
.subscribe(videoData => {
console.log(videoData);
//Here how to I join videoData into each item inside of data.items.
});
});
});
So the above code does work. But I still have these two questions:
How do I join the videoData
back to data.items
and make sure the correct item inside the data.items
is joined with the correct videoData
(the videoData
that is using the entry.id.videoId
of the related item inside data.items
) and create that new newArray
?
How do I know when everything is done and do something based on all the data after all the async requests have finished?
The newArray keep the same order of the first HTTP request. As the first HTTP hit the YouTube search list API with order by viewCount. Simply nest observable like above will not keep the original order.
UPDATE
With inoabrian solution, I can successfully achieve the above three points. But when I call the myvideoservice again (like changing the url as new NextPageToken), the Subject - this._videoObject has 2 observers. And load again, it has 3 observers and so on. I need a way to reset the Subject to have only 1 observers so I won't have duplicate videos. Is it a best practice way to clear/reset subject?
map() algorithm applies an async callback to each element of an array, creating promises as it does. However, the returned result by . map() is no promise, but an array of promises.
To make the function asynchronous, we need to do three changes: Add async keyword to the function declaration. Instead of calling then() on the promise, await it and move the callback code to main function body. The promise result required in the callback will be returned by the await call.
So, that means you can't use async-await in the map. But to achieve similar functionality we can use some other function in a javascript library to make sure the array through which we are iterating must be resolved instead of the function call. So, the solution is to use Promise. all() function in javascript.
An asynchronous data stream is a stream of data where values are emitted, one after another, with a delay between them. The word asynchronous means that the data emitted can appear anywhere in time, after one second or even after two minutes, for example.
// You need to set up a subject in your sevice
private _videoObject = new Subject < any > ();
videoDataAnnounced$ = this._videoObject;
let url = [YouTube API V3 list url]
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics';
this.http.get(url)
.map(res => res.json())
.subscribe(data => {
this.cache = data.items;
data.items.forEach(entry => {
let videoUrl = detailUrl + '&id=' + entry.id.videoId + '&key=' + this.googleToken;
this.http.get(videoUrl)
.map(videoRes => videoRes.json())
.subscribe(videoData => {
// This will announce that the full videoData is loaded to what ever component is listening.
this._videoObject.next(videoData);
});
});
});
// In your constructor for your component you can subscribe to wait on the data being loaded.
public newVideos: any[] = [];
constructor(private myServiceToLoadVideos: myvideoservice) {
let self = this;
this.myServiceToLoadVideos.videoDataAnnounced$.subscribe((video) => {
// Here you could loop through that
self.myServiceToLoadVideos.cache.map(d => {
if (d.id == video.id) {
// Here you have the old data set without the details which is (d) from the cached data in the service.
// And now you can create a new object with both sets of data with the video object
d.snippet = video.items[0].snippet;
d.statistics = video.items[0].statistics;
d.contentDetails = video.items[0].contentDetails;
this.posts = this.posts.concat(d);
}
});
if(this.posts.length == this.cache.length){
this.posts.sort(function(l,r){
// if l is < r then it will return a negative number
// else it will return a positive number.
// That is how this sort function works
return l.viewCount - r.viewCount;
});
// Here you will have all of the data and it will be sorted in order.
}
});
}
UPDATE -- -- --
The Subject is an observable - http: //reactivex.io/documentation/subject.html When you listen to the Subject it is not like q.all it will complete one by one until they complete. The cache in the service call is just to keep track of the playlist. Then when you receive the details in the constructor you can match them back up and keep order with the cache. Q: "And when subscribe to the subject as in your code, the result is when ALL the " next " in _videoObject finished, right?" A: No, when each individual finishes you will get once at a time.
JS Sort - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With