Reputation: 2599
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
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
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