#Effect()
initDomain$: Observable<Action> = this.actions$.pipe(
ofType('INIT_DOMAIN'),
mergeMap((action: any) =>
this.http.get('https://demo.api/url1.php').pipe(
switchMap((data) => [
{type: 'INIT_IT', payload: data}
]),
catchError(() => of({type: 'INIT_IT_FAILED'}))
)
)
);
I have this angular effect (ngrx) that makes 1 request before continue. How can I make 2 requests and wait for both responses before continue? I know that forkJoin() is the answer but I'm little confused about the syntax
forkJoin(
this.http.get('myUrl'),
this.http.get('myOtherUrl')
)
OR if you have a bundle of observables in an array you could also write
const myArrayOfObservables = [
this.http.get('myUrl'),
this.http.get('myOtherUrl')
];
forkJoin(
myArrayOfObservables
)
This is because "forkJoin" uses the "spread" (...args) operator for its params.
Related
I need to invoke multiple web request in declared order. Every consecutive request depends on the outcome of previous one. I would like to emit event after each request is completed that will go to the subscriber.
Right now I am "tapping" value of each request and emitting result using separate Subject. Is it possible to do this using single pipe with operators?
Here is code example
fromEvent(pauseButton, "click")
.pipe(
tap(()=>{
subscribedLabel.innerHTML="";
tapedLabel.innerHTML="";
}),
tap(v => (tapedLabel.innerHTML = "started")),
concatMapTo(of("phase 1 completed").pipe(delay(1000))),
tap(v => (tapedLabel.innerHTML = v)),
concatMapTo(of("phase 2 completed").pipe(delay(1000))),
tap(v => (tapedLabel.innerHTML = v))
)
.subscribe(v => {
console.log(v);
subscribedLabel.innerHTML = v;
});
https://stackblitz.com/edit/typescript-6jza7h?file=index.ts
The expected outcome is that subscribedLabel.innerHTML will change the same way as tapedLabel.innerHTML
It not clear what you're after, but this is a way you can use 4 consecutive calls and accumulate all their responses into one object.
function fakeHTTP(resW): Observable<string> {
return of(resW).pipe(delay(1000))
}
fromEvent(button, "click").pipe(
concatMap(_ =>
fakeHTTP(1).pipe(
map(res => ({first: res}))
)
),
tap(_ => console.log("First Request Complete")),
concatMap(first =>
fakeHTTP(2).pipe(
map(res => ({...first, second: res}))
)
),
tap(_ => console.log("Second Request Complete")),
concatMap(second =>
fakeHTTP(3).pipe(
map(res => ({...second, third: res}))
)
),
tap(_ => console.log("Third Request Complete")),
concatMap(third =>
fakeHTTP(4).pipe(
map(res => ({...third, fourth: res}))
)
),
tap(_ => console.log("Fourth Request Complete"))
).subscribe(console.log);
The output of this is as follows:
// Wait 1s
First Request Complete
// Wait 1s
Second Request Complete
// Wait 1s
Third Request Complete
// Wait 1s
Fourth Request Complete
{"first":1,"second":2,"third":3,"fourth":4} // <- Value sent to subscribe
Update #1: Pass Values Up the Call Chain
You can pass values up the call chain, but it gets a bit more complicated. You want each step only to work on values from the previous step, but to ignore (emit unaltered) the values from further up the chain.
One way you can do this is to tag each response. I do this with a pass flag that can be true or false. The final operation is to remove the flag.
Here is what that looks like:
function fakeHTTP(resW): Observable<string> {
return of(resW).pipe(delay(1000))
}
fromEvent(button, "click").pipe(
concatMap(_ =>
fakeHTTP(1)
),
tap(_ => console.log("First Request Complete")),
concatMap(first =>
fakeHTTP(2).pipe(
map(res => ({pass: false, payload: res})),
startWith({pass: true, payload: first})
)
),
tap(({pass}) => {
if(!pass) console.log("Second Request Complete")
}),
concatMap(second => second.pass ?
of(second) :
fakeHTTP(3).pipe(
map(res => ({pass: false, payload: res})),
startWith({...second, pass: true})
)
),
tap(({pass}) => {
if(!pass) console.log("Third Request Complete")
}),
concatMap(third => third.pass ?
of(third) :
fakeHTTP(4).pipe(
map(res => ({pass: false, payload: res})),
startWith({...third, pass: true})
)
),
tap(({pass}) => {
if(!pass) console.log("Second Request Complete")
}),
map(({payload}) => payload)
).subscribe(console.log);
The output of this is as follows:
// Wait 1s
First Request Complete // <- console log from tap
1 // <- console log from subscribe
// Wait 1s
Second Request Complete // <- console log from tap
2 // <- console log from subscribe
// Wait 1s
Third Request Complete // <- console log from tap
3 // <- console log from subscribe
// Wait 1s
Second Request Complete // <- console log from tap
4 // <- console log from subscribe
Update #2: When recursion is possible
You can also make recursive calls where each new call depends on the previous call and some base-case ends the recursion. RxJS jas expand as a built-in way to recurse.
In this example, each new call to fakeHTTP uses the value emitted by the previous call directly.
function fakeHTTP(resW): Observable<string> {
return of(resW).pipe(delay(1000))
}
fromEvent(button, "click").pipe(
map(_ => 1),
expand(proj => proj < 4 ?
fakeHTTP(++proj) :
EMPTY
)
).subscribe(console.log);
The output of this is as follows:
// Wait 1s
1
// Wait 1s
2
// Wait 1s
3
// Wait 1s
4
Update #3: Separate observables
function fakeHTTP(resW): Observable<string> {
return of(resW).pipe(delay(1000))
}
const first$ = fromEvent(button, "click").pipe(
concatMap(_ => fakeHTTP(1)),
share()
);
const second$ = first$.pipe(
concatMap(first => fakeHTTP(2)),
share()
);
const third$ = second$.pipe(
concatMap(second => fakeHTTP(3)),
share()
);
const fourth$ = third$.pipe(
concatMap(third => fakeHTTP(4))
);
merge(
first$,
second$,
third$,
fourth$
).subscribe(console.log);
Here's another, more annoying way to write almost the exact same thing.
function fakeHTTP(resW): Observable<string> {
return of(resW).pipe(delay(1000))
}
fromEvent(button, "click").pipe(
map(_ => fakeHTTP(1).pipe(
share(),
)),
map(first$ => ([first$.pipe(
concatMap(firstR => fakeHTTP(2)),
share()
), first$])),
map(([second$, ...tail]) => ([second$.pipe(
concatMap(secondR => fakeHTTP(3)),
share()
),second$, ...tail])),
map(([third$, ...tail]) => ([third$.pipe(
concatMap(thirdR => fakeHTTP(4))
),third$, ...tail])),
concatMap(calls => merge(...calls))
).subscribe(console.log);
I have an epic, that listens for a certain action.
Once it gets the action, it should do a ajax.post
Branch
If status code is good, then emit YES
If status bad, then emit pre, wait 1s, emit post
I am struggling mightily with the last bullet, here is my code in a playground - https://rxviz.com/v/WJxGMl4O
Here is my pipeline part:
action$.pipe(
flatMap(action =>
defer(() => ajax.post('foo foo foo')).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
retryWhen(error$ =>
error$.pipe(
tap(error => console.log('got error:', error)),
merge(of('pre')), // this isnt emiting
delay(1000),
merge(of('post')) // this isnt emitting
)
)
)
)
)
I think you can achieve what you want by using catchError instead of retryWhen because retryWhen only reacts to next notifications but won't propagate them further. With catchError you get also the source Observable which you can return and thus re-subscribe. concat subscribes to all its source one after another only after the previous one completed so it'll first send the two messages pre and post and after that retry.
action$.pipe(
filter(action => action === 'hi'),
mergeMap(action =>
defer(() => resolveAfter(3)).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
catchError((error, source$) => {
console.log('retrying, got error:', error);
return staticConcat(
of('pre'),
of('post').pipe(delay(1000)),
source$,
);
}),
)
),
//take(4)
)
Your updated demo: https://rxviz.com/v/A8D7BzyJ
Here is my approach:
First, I created 2 custom operators, one that will handle 'pre' & 'post'(skipValidation) and one that will handle the logic(useValidation).
const skipValidation = src => of(src).pipe(
concatMap(
v => of('post').pipe(
startWith('pre'),
delay(1000),
),
),
);
What's important to notice in the snippet below is action$.next({ skip: true }). With that, we are emitting new values that will go through the iif operator so that we can emit 'pre' & 'post';
const useValidation = src => of(src).pipe(
filter(action => action === 'hi'),
mergeMap(action =>
defer(() => resolveAfter(3)).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
delay(1000),
retryWhen(error$ =>
error$.pipe(
tap(error => { console.log('retrying, got error:', error); action$.next({ skip: true })}),
delay(1000),
)
)
)
)
);
action$.pipe(
tap(v => console.log('v', v)), // Every emitted value will go through the `iif ` operator
mergeMap(v => iif(() => typeof v === 'object' && v.skip, skipValidation(v), useValidation(v))),
)
Here is your updated demo.
I'm a beginner in RxJS so sorry if this doesn't make sense.
The current flow in time looks like this:
REMOVE_USER -----------------------> SUCCESS
---------------GET_DEVICE--->SUCCESS--------
The high-level goal is to skip fetching device when user is being removed.
Oversimplified epics:
const getDeviceEpic = action$ => action$.pipe(
ofType('GET_DEVICE_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'GET_DEVICE_SUCCESS' }))
))
const removeUser = action$ => action$.pipe(
ofType('REMOVE_USER_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'REMOVE_USER_SUCCESS' }))
)
)
How would I approach that?
I'm not sure if I can somehow add e.g. takeUntil(removeUserAPICall$) to device app. Or perhaps check if REMOVE_USER_REQUEST has been fired and then wait for REMOVE_USER_SUCCESS to continue.
Potentially, you could achieve that with windowToggle:
In your case "on" is REMOVE_USER_SUCCESS and "off" is REMOVE_USER_REQUEST.
So we'll be listening to GET_DEVICE_REQUEST between REMOVE_USER_SUCCESS and REMOVE_USER_REQUEST.
Note that we'll have to start with our filter open, by prepending startWith(void 0) to the "on" stream.
E.g.:
const getDeviceEpic = action$ => action$.pipe(
ofType('GET_DEVICE_REQUEST'),
windowToggle(
action$.pipe(ofType('REMOVE_USER_SUCCESS'), startWith(void 0)),
()=>action$.pipe(ofType('REMOVE_USER_REQUEST')
),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'GET_DEVICE_SUCCESS' }))
))
const removeUser = action$ => action$.pipe(
ofType('REMOVE_USER_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'REMOVE_USER_SUCCESS' }))
)
)
* warning: written in a notepad
Yet, imho, it's also fine to have a flag on the store (maybe, as an indication of the state).
More about pausing and muting streams in my article "Pausable Observables in RxJS".
--
Hope this helps
I am trying to sequence two ajax requests in redux-observables, and second request will be based on response of first, And i individually want to dispatch response or error of both requests.
Using concat to process one request after another but i am stuck on how to use response of first one into second request. If i chain using two switchMap then through result selector i will get inner and outer observables but i am not getting how to dispatch action after first request.
export const deleteSettingsEpic = action$ => action$.pipe(
ofType('DELETE_SETTINGSDATA'),
switchMap(action$ => concat(
ajax.ajaxDelete(`${action$.payload.api}/${action$.payload.id}`)
.pipe(
map(r => ({ type: 'DELETE_SUCCESS', payload: r })),
catchError(e => of({ type: 'DELETE_ERROR' }))
),
ajax.get(`${action$.payload.api}?page=${action$.payload.query.page}&limit=${action$.payload.query.limit}`)
.pipe(
map(r => ({
type: action$.payload.getPaginationAction,
payload: {
data: r.response.docs,
page: r.response.page,
totalPages: r.response.totalPages,
limit: r.response.limit,
totalDocs: r.response.totalDocs
}
})),
catchError(e => of({ type: 'FETCH_ERROR' }))
),
of({ type: 'SET_DIMMER_FALSE' }).pipe(delay(250)),
of({ type: 'RESET_ERRORS' }).pipe(delay(1500)),
)
),
);
Probably you should use concatMap for the second ajax request, in pseudo code:
export const deleteSettingsEpic = action$ => action$.pipe(
ofType('DELETE_SETTINGSDATA'),
switchMap(action$ => ajax.ajaxDelete(...)),
concatMap(result => ajax.get(...))
),
);
I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);