Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do a async HTTP request inside an Angular2 loop/map and modify the original loop array?

Tags:

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:

  1. 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?

  2. How do I know when everything is done and do something based on all the data after all the async requests have finished?

  3. 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?

like image 540
Hugh Hou Avatar asked Jul 01 '16 23:07

Hugh Hou


People also ask

Can you use async in map function?

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.

How do I make asynchronous call in angular 8?

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.

Can we use await in Map Javascript?

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.

What is asynchronous data in angular?

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.


1 Answers

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

like image 162
inoabrian Avatar answered Sep 28 '22 03:09

inoabrian