Amol Borkar
Amol Borkar

Reputation: 2599

Observable for mutiple responses in angular 2

So, I have this service which first calls a function from another module which basically returns an list of urls from an external api. This service then must http.get from all the urls in that list (every url returns a json object of same format) then return a single observable which I can then use in an angular component. Here's what my service code looks like:

    import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';
    import { Client } from 'external-api';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/map';

    let client = new Client();

    @Injectable()
    export class GbDataService {
      constructor(private _http: Http) {

      }

        getGBData(): Observable<any> {
        client.fetchUrls("").then(resp=> {
        resp.forEach(url => {
          //this._http.get(url).map(res => res.json);
          // Create a single observable for every http response

        });
        }).catch(function(err){
            console.log(err.message);
        });
        //return observable
      };
   }

http.get returns and Observable<Response> type but I couldn't find a way to create and return one Observable for all http.get responses. How can this be done ? Should I create an observable array then push() all the get response I get to the array?

EDIT: It doesn't really matters to me if responses are emitted one by one or all at once BUT there must be only a single Obeservable which emits the responses of all the http.get requests.

Further Edit: This is my fetchURLs method:

  fetchURLs(): Promise<any> {
    return new Promise((resolve, reject) => {
      let _repos: Array<any>;
      //scrapeTrendingRepos() is from https://github.com/rhysd/node-github-trend
      scraper.scrapeTrendingRepos("").then(repos => {
        repos.forEach(repo => {
          let url = `https:api.github.com/repos/${repo.owner}/${repo.name}`;
          _repos.push(url);
        });
        resolve(_repos);
      }).catch(err => {
        reject(err);
      });
    })
  };

Have I implemented Promises in fetchURLs() right??

Upvotes: 0

Views: 788

Answers (2)

snorkpete
snorkpete

Reputation: 14564

So, you make a request and get back an array of URLs that you then want to fetch all and get one response from?

Those are the types of things that RxJS excels at.

@Injectable()
    export class GbDataService {
      constructor(private _http: Http) {

      }

      getGBData(): Observable<any> {

        return Observable
          .fromPromise(client.fetchUrls())   // returns Observable<array>
          .switchMap( urls => {  
            // convert your list of urls to a list of http requests
            let urlRequests = urls.map( url => http.get(url) );

            // combineLatest accepts an array of observables,
            // and emits an array of the last results of each of the observables
            // but the first emit only happens after every single observable
            // has emitted its first result

            // TLDR: combineLatest takes an array of Observables
            //       and will emit an array of those observable emissions       //       after all have emitted at least once
            return Observable.combineLatest(urlRequests);

          })
        }).catch(function(err){
            console.log(err.message);
        });
        //return observable
      };
   }

Further info:

Read up on the combineLatest observable. In this scenario, it accomplishes what you want of waiting for all its observable arguments to emit before emitting a single array. But if your observable arguments also emit multiple times, it may not do what you expect and you might want to try a different operator like forkJoin or zip.

Additionally

You might want to use switchMap rather than flatMap - if a new request for urls to fetch comes through, switchMap will cancel any requests currently in flight before sending the new list of requests.

Further Edit

Although your fetchURLs implementation can work in its current incarnation, you can simplify your method a bit if you wish by taking advantage of how promises work. In 'Promise-land', the then handler also returns a Promise, and that second Promise will resolve with whatever value you return from your then handler (this is the basic promise chaining concept). Using that knowledge, you can simplify your method to:

fetchURLs(): Promise<any> {
  //scrapeTrendingRepos() is from https://github.com/rhysd/node-github-trend
  return scraper.scrapeTrendingRepos("").then(repos => {
     // since repos is an array, and you wish to transform each value
     // in that array to a new value, you can use Array.map
     return repos.map( repo => `https:api.github.com/repos/${repo.owner}/${repo.name}`);
  });
}

Upvotes: 3

El houcine bougarfaoui
El houcine bougarfaoui

Reputation: 37343

if client.fetchUrls("") return a native Promise you may want to use snorkpete solution.

if not try to create an observable:

getGBData(): Observable<any> {

   return Observable.create(observer => {
        client.fetchUrls("").then(resp=> {
        resp.forEach(url => {
          this._http.get(url).map(res => res.json).subscribe(data=>{
            observer.next(data);
          });


        });
        }).catch(function(err){
            console.log(err.message);
            observer.error(err);
        });
  });
}

Upvotes: 1

Related Questions