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??
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}`);
});
}
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);
});
});
}
Related
I am new to RxJS and I am trying to subscribe to an observable function in two different and I am wondering if there is a way to trigger a call from one of the functions can also change the outcome in the second file.
I have an action creator and authGuard subscribe to my loginService and I am trying the action creator will trigger once I call the login function from the auth guard.
action.js
this.loginService.handleLogin(userId)
.subscribe((data) => {
console.log("response in action.js", response);
},
(e) => {
console.log(e);
});
authGuard.js
this.loginService.handleLogin(userId)
.subscribe((response) => {
console.log("response in authGuard.js", response);
}, (err) => {
console.log("error", err);
})
loginService.js
handleLogin(userId) {
const url = `api/user/${userId}`;
return this.http.get(url, { headers: this.headers })
.map((response: Response) => {
return response.json();
})
.catch((e) => {
return Observable.throw(e);
});
}
expectation:
I am expecting to get console.logs results in action.js and authGuard.js when I call handlLogin function of loginService from either file.
Each time you call handleLogin, a separate observable is being created and returned. So your two files are not subscribed to the same object.
Take a look at the answers here for how to structure your handleLogin implementation to fix this: What is the correct way to share the result of an Angular Http network call in RxJs 5?. Note in particular this answer about shareReplay() which is probably the better up to date answer though it's not the highest scored: https://stackoverflow.com/a/43943217/518955
The HTTP observable makes an HTTP request for each subscriber. To share a single HTTP request for multiple observers, you need something like the share operator.
export class FetchService {
data$: Observable<any>;
constructor(){
this.data$ = this._http.request(this._url)
.map((res:Response)=>res.json())
.catch(this.errorHandler)
.share();
}
getData(){
return this.data$;
}
}
Now the multiple observers will share the same observable.
This operator is a specialization of publish which creates a
subscription when the number of observers goes from zero to one, then
shares that subscription with all subsequent observers until the
number of observers returns to zero, at which point the subscription
is disposed.
I am trying to do a basic chaining of RxJS HTTP calls where the first call is going to pass the ID of an object that was created server-side into the second call. What I am observing with my implementation is that only the first call is made and the second chained call is never executed.
Below is roughly the code that I have.
First call:
createItem(itemData) {
return this.http.post(createUrl, itemData)
.map((res) => res.json())
.catch(this.handleError);
}
Second call:
addFileToItem(id, file): Observable<any> {
return this.http.post(secondUrl + id, file)
.map((res) => log(res))
.catch(this.handleError);
}
Function defined mapping one call into the other:
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.map((res) => {
if (file) {
return addFileToItem(res.id, file);
}
});
}
And finally, where the observable is executed through a subscribe():
someFunction(itemData, file) {
createAndUpload(itemData, file)
.subscribe(res => {
log(res);
//go do something else
};
}
The problem is in createAndUpload where map just turns the result from the first call into an Observable but you never subscribe to it.
So simply said you just need to use mergeMap (or concatMap in this case it doesn't matter) instead of map. The mergeMap operator will subscribe to the inner Observable and emit its result:
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.mergeMap((res) => {
if (file) {
return addFileToItem(res.id, file);
}
return Observable.empty(); // you can't return `undefined` from the inner Observable
});
}
Apparently the map() function doesn't actually return anything, meaning that an Observable object is not returned. Instead, a transforming operator such as mergeMap is needed.
Here is the code that ended up working, also using the "newer" .pipe() operator. The only place the code needed to be changed was in the Observable function where I defined the combination of the two separate Observables. In order to access the mergeMap operator, don't forget to import it from rxjs/operators.
For a reason that I will eventually figure out, I couldn't access the mergeMap without the pipe...
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.pipe(
mergeMap((res) => {
if (file) {
return addFileToItem(res.id, file);
}
})
)
}
I would like to wait for all my http request to be completed before doing something.
Before Angular 5, I was using promises and Promise.All.
With Angular 5 and the new HttpClient, I transformed my promises into observables. If I understand correctly, I now have to use forkJoin to replace the Promise.All.
But this is a problem because forkJoin expect Observables as parameters, but I already subscribe to those in the code
ngOnInit() {
forkJoin([this.getTemplates(), this.getHistory()]).subscribe(
results => {
this.isLoading = false;
}
);
}
getTemplates(): Observable<any> {
return this.notifService.getTemplateList()
.subscribe(
response => {
if (response.code === 0) {
this.templateList = response.data;
}
else {
this.openSnackBar(response.formatError());
}
},
error => {
this.openSnackBar(error);
});
}
I can't do the logic inside the subscription of the forkJoin because those method (getTemplates() & getHistory()) need to be standalone and called seperatly in other processes.
So, what can I do to be sure that all the subscriptions are done ?
By the way, the code above doesn't compile because the method getTemplates() return a Subscription and not an Observable
Use map Instead of subscribe in getTemplates:
getTemplates(): Observable<any> {
return this.notifService.getTemplateList()
.map(response => {
/* ... */
return reponse;
});
}
Using RxJs 5 and Angular 4.
I want to share an observable so that I only make 1 Http request, and I also want to await the calls so that I get the result when I request it. I have the following code:
export class DataService {
constructor(public http: HttpClient) {
this.getObservable();
}
public observable;
public getObservable() {
const url = "api/getData";
this.observable = this.http.get(`${this.baseUrl}${url}`).share()
}
public async hasData(data: DataEnum) {
const result = await this.observable.toPromise();
return result.filter(x => x === data).length > 0;
}
}
However many calls to hasData is resulting in many calls to our api end point. I am assuming that I have set observable to a shared observable, and when I call .toPromise() it will just get the cached value and make it a promise, which I can await.
Is this how it should work?
Due to how share works, observable is resubscribed on toPromise, ths produces new requests.
Promises already provide caching behaviour. Considering that promises are already using in service API, they can be used exclusively:
constructor(public http: HttpClient) {
this.getPromise();
}
public promise;
public getPromise() {
const url = "api/getData";
this.promise = this.http.get(`${this.baseUrl}${url}`).toPromise()
}
public async hasData(data: DataEnum) {
const result = await this.promise;
return result.filter(x => x === data).length > 0;
}
Your code seems overly complex to me. I would likely do something like:
private data = null;
getData():Observable<> {
// if data is already available, return it immediately
if (this.data) return Observable.of(this.data);
// else, fetch from the server and cache result
return this.http.get(url).do(data => this.data=data)
}
So whenever you want the data you just do:
this.getData().subscribe(
data => console.log(data);
)
To be sure that you won't call your API endpoints multiple times before the data arrives, you have a few options.
Look into data resolvers -- these will not init your component until the data has arrived. In ngOnInit the data will be ready synchronously so no risk of calling the server multiple times.
Alternatively, you can hide the view until the data is ready with *ngIf="data" so a user won't click a button multiple times.
I have created an authentication guard for my angular2 rc5 application.
I am also using a redux store. In that store I keep the user's authentication state.
I read that the guard can return an observable or promise (https://angular.io/docs/ts/latest/guide/router.html#!#guards)
I can't seem to find a way for the guard to wait until the store/observable is updated and only after that update return the guard because the default value of the store will always be false.
First try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// updated after a while ->
this.authenticated$.subscribe((auth) => {
// will only reach here after the first update of the store
if (auth) { resolve(true); }
// it will always reject because the default value
// is always false and it takes time to update the store
reject(false);
});
});
}
}
Second try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// tried to convert it for single read since canActivate is called every time. So I actually don't want to subscribe here.
let auth = this.authenticated$.toPromise();
auth.then((authenticated) => {
if (authenticated) { resolve(true); }
reject(false);
});
auth.catch((err) => {
console.log(err);
});
}
}
When you subscribe to an observable, you can provide a callback function; in the example below, I call it CompleteGet. CompleteGet() will only be invoked on a successful get that returns data and not an error. You place whatever follow on logic you need in the callback function.
getCursenByDateTest(){
this.cursenService
.getCursenValueByDateTest("2016-7-30","2016-7-31")
.subscribe(p => {
this.cursens = p;
console.log(p)
console.log(this.cursens.length);
},
error => this.error = error,
() => this.CompleteGet());
}
completeGet() {
// the rest of your logic here - only executes on obtaining result.
}
I believe you can also add a .do() to the observable subscription to accomplish the same thing.
all you need to do is force the observable to update:
canActivate(): Observable<boolean> {
return this.authenticated$.take(1);
}
Edit:
canActivate waits for the source observable to complete, and (most likely, I don't know what happens behind the scenes), the authenticated$ observable emits .next(), not .complete()
From documentation: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take
.take(1) method takes first value emitted by the source observable and then completes
Edit2:
I just looked at snippet you pasted, and I was right - the store.select() observable never completes, it always emits .next
Subscribe doesn't return an Observable.
However, you can use the map operator like that:
this.authenticated$.map(
authenticated => {
if(authenticated) {
return true;
}
return false;
}
).first() // or .take(1) to complete on the first event emit