RxJS subscribing to the observable in two different files - javascript

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.

Related

Where should I call the API ? (Thunks?, Component ? )

I have a question about React with Redux, all concepts of redux they have me confused bit, I've watch tutorial or papers where in actions file add thunk functions for all process of call api loading, success or fail and then save result in store, my question is when I must do this or just call api in my component ? is a good practice call API in my componet ?
Thank!
Sorry, I don't speak english very well, I hope they have understood.
You have a couple of options when it comes to api calls in react/redux. Here's two that I've used:
1.Make all calls in action creator with redux-thunk:
const action = () => async dispatch => {
try {
const {data} = await axios.get('/endpoint');
dispatch({type: DO_SOMETHING, payload: data})
} catch (e) {
handleError(e)
}
}
This method works well and there's nothing wrong with it. The problem is that you end up writing a lot of boilerplate code. It also means that your action creators aren't pure functions. Pure actions are generally easier to test and reason about.
2.Use an action as a command that contains relevant api call information and a success handler that is invoked with the response. You can then write middlware that handles all your api calls in one place. This makes it easier to handle errors and keeps action creators pure. This method is a little more setup but it's worth it in the long run.
action creator that component dispatches:
const getSomthing = () => ({
type: API,
payload: {
call: {
url: "/endpoint",
method: "get"
},
success: data => ({type: DO_SOMETHING, payload: data})
}
});
middlware that handles api calls:
const api = ({ dispatch, getState }) => next => async action => {
if (action.type !== API) {
return next(action);
}
const { call, success, failure } = action.payload;
try {
const { data } = await axios(call);
if (success) {
dispatch(success(data));
}
} catch (e) {
handleError(e)
}
};
You can then apply this middleware to your store.
Boris Dinkevich uses this approach. I'll link to his talk about it which is worth a watch regardless of which method you use. https://www.youtube.com/watch?v=Gjiu7Lgdg3s

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??
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);
});
});
}

Angular 2 calling multiple async methods

I have a mobile app I'm building and right now I'm working on authentication. Before I hit my home page I need to hit a variety of endpoints on an API I've built before I can display data to the user.
All the endpoints are returning the correct data when tested in Postman, however I'm getting a null value in my second async call when I utilize it in my app.
I'm sure it has something to do with the order in which these calls are made, so I was just looking for some help as to how I can properly wait for one call to finish before starting another one.
public login() {
this.showLoading();
this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
.subscribe(
res => {
console.log(res);
localStorage.setItem("UserId", res.toString());
},
err => {
console.log(err);
});
this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL
.subscribe(
res => {
console.log(res);
localStorage.setItem("EmployeeId", res.toString());
},
err => {
console.log(err);
});
this.authService.login(this.registerCredentials)
.subscribe(
data => {
this.loading.dismissAll();
console.log('User logged in successfully! ', data);
this.nav.push(TabsPage);
localStorage.setItem("Username", this.registerCredentials.username);
localStorage.setItem("isLoggedIn", "true");
},
error => {
this.loading.dismissAll();
this.showAlert("Uh oh!", "Something went wrong. Please re-enter your login credentials or check your connection.");
console.log(error);
});
}
Your original code has a bug that leads to this error. You have three calls in your code which I will call A), B), and C):
A) this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
B) this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL
C) this.authService.login(this.registerCredentials)
What you need to understand about RXJS is the difference between a cold Observable (which represents all information required to start an async operation) and a hot Observable (which is an Observable with the async operation already started).
The three calls A), B) and C) merely build cold observables which are started the moment you call .subscribe() on them. So by the time B) is built, A) is already started but has not completed yet. So the call to localStorage.getItem("UserId") will return null, because A) has not yet invoked its subscriber's next callback.
So what you want to do is for B) to wait on A). Also instead of stuffing something into global state (localStorage) it's probably better to flow the result from A) through to B). Enter the .mergeMap() operator:
this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
.map(res => res.toString())
.do(userId => localStorage.setItem("UserId", userId)) // cleanly separate side-effects into .do() calls
.mergeMap(userId => this.userService.getEmployeeIdFromUserId(userId))
.map(res => res.toString())
.do(employeeId => localStorage.setItem("EmployeeId", employeeId))
.subscribe(
employeeId => {
console.log(employeeId);
},
err => {
console.log(err);
});
The nice thing about rxjs is that error handling just works all the way through your Observable chain.
If you can execute C) in parallel, have a look at .forkJoin().
Finally, if you need a hands on explanation of .mergeMap() have a look at this answer: SwitchMap vs MergeMap in the #ngrx example
This should work.Don't forget import 'rxjs/Rx'
this.userService.getUserIdFromUserName(this.registerCredentials.username)
.map(res => res.toString())
.do(userId => {
console.log(res);
localStorage.setItem("UserId", userId);
})
.flatMap(userId => {
return this.userService.getEmployeeIdFromUserId(userId);
})
.do(res => {
console.log(res);
localStorage.setItem("EmployeeId", res.toString());
})

How to use an observable in angular 2 guards' canActivate()

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

React/Redux , how to "chain" actions for http call when http call is in middleware

I am setting up a basic app to call the server for some stuff. the one catch, is I have made my server calls as a piece of middleware so other devs I work with can use it. I can make the call - however I would like to add "loading", "success", and "error" actions to it. Before - I could easily do this by placing the calls in the action creator itself, something like this for example :
//in the action creator
export function fetchData() {
return dispatch => {
request
.get('/myApi')
.end((err, res) => {
if (err) {
dispatch({
type: LOADING_ERROR,
error: err
});
} else {
let myData = JSON.parse(res.text);
dispatch({
type: LIST_DATA,
myData
});
}
});
dispatch({
type: LOADING_DATA
});
};
this worked great for me for having the loading/success/error accessible on the ui (so i can do things like show/hide a loader and such).
In this new project, I have my calls in a piece of middleware like so :
//the middleware
export default (actionObject) => (store) => (next) => (action) => {
switch(action.type) {
case actionObject.LIST:
let constructedURL = actionObject.URL + "?query=&context=" + actionObject.context;
request
.get(constructedURL)
.end((err, res) => {
if (err) {
return next(action);
}
action[options.resultAction] = res.body;
return next(action);
});
break;
default:
return next(action);
}
}
So the actionObjectis just an object you pass to let you set up this middleware so it will work with your own actions/reducers, but it is just matching your defined action to make the call. Now the issue is I don't know how I can get back into the loading/success/error actions from here. I have them set up in my actions and reducers but I am unsure how to execute it in this manner.
I was first looking at something like this - https://gist.github.com/iNikNik/3c1b870f63dc0de67c38#file-helpers-js-L66-L80 , however I am not sure if this is exactly what I am trying to do. I am not sure where I can tell it to fire the action DATA_LOADING, and then in success of the call fire either LIST_DATA or LOADING_ERROR. Could use any advice, and thank you for reading!

Categories

Resources