Managing subscriptions based on async event - asynchronous

Im looking a way to simplify this and avoid managing subscriptions inside my pipe.
The general idea abought my code:
this.start$ubject // waiting for users call to start
.pipe(
mergeMap(() => from(this.thirdPartyService.start())), // now i need to wait for smth to start
tap(() => {
// only after thirdPartyService started i can subscribe because prior to that they are undefined
this.subscriptions.push(
this.thirdPartyService.alfa$.subscribe(this.first$ubject),
this.thirdPartyService.beta$.subscribe(this.second$ubject),
);
}),
);
Is there any way to deal with that? Something like takeWhile but for subscription?

try like that:
// waits for start$ubject, then waits for thirdPartyService, then starts.
this.subscriptions.push(
this.start$ubject.pipe(
switchMap(() => from(this.thirdPartyService.start())), // waiting start
switchMap(() => merge( // listening on both of the streams
this.thirdPartyService.alfa$.pipe(
tap(this.first$ubject),
),
this.thirdPartyService.beta$.pipe(
tap(this.second$ubject),
),
)),
).subscribe(),
);
// waits for start$ubject or for thirdPartyService, then starts.
this.subscriptions.push(
merge(this.start$ubject, from(this.thirdPartyService.start()).pipe(
switchMap(() => merge( // listening on both of the streams
this.thirdPartyService.alfa$.pipe(
tap(this.first$ubject),
),
this.thirdPartyService.beta$.pipe(
tap(this.second$ubject),
),
)),
).subscribe(),
);

Related

Multiple async action execution order in single epic

I'm trying to setup loginEpic which occurs when user logs in. This is how the logic flow should work:
Epic should start with LOGIN_REQUEST action. Once login promise is finished successfully, user info should be fetched with SYNC_USER_REQUEST action (which is basically whole other epic since this is also called when initially entering site to get user info or redirect to login). Once that finishes successfully (promise and SUCCESS/FAIL calls are handled within syncUserEpic), SYNC_USER_SUCCESS should be caught in loginEpic and LOGIN_SUCCESS should be called along with push action which redirects user to starting page.
This is what I have so far:
const loginEpic: Types.RootEpic = (action$) =>
action$.ofType("LOGIN_REQUEST").pipe(
switchMap(({ payload }) =>
from(membershipService.login(payload.username, payload.password)).pipe(
switchMap((response) => [
syncUser.request(),
action$.ofType("SYNC_USER_SUCCESS").pipe( // I think this is the problem
filter(isActionOf(syncUser.success)),
map(r => login.success(response))
)
]),
takeUntil(action$.ofType(["LOGOUT_REQUEST", "LOGIN_CANCEL"])),
catchError(error => of(login.failure(error))),
endWith(push("/"))
)
)
)
but I'm getting
Actions must be plain objects. Use custom middleware for async
actions.
I'm also cancelling LOGIN_REQUEST/SYNC_USER_REQUEST with LOGOUT_REQUEST or LOGIN_CANCEL actions and also handling login error (I'm not sure if I should also handle sync user error here)
I've managed to implement it this way:
const loginEpic: Types.RootEpic = (action$) =>
action$.pipe(
filter(isActionOf(login.request)),
switchMap(({ payload }) =>
race(
from(membershipService.login(payload.username, payload.password)).pipe(
mergeMap((token) =>
from(membershipService.getUser()).pipe(
mergeMap((user) => [syncUser.success(user), login.success(token)]),
catchError(error => {
// user fetch failed, tell login that user is responsible
return of(login.failure(new BaseError("6010", "User sync failed.")));
})
)
),
catchError(error => of(login.failure(error))),
),
action$.pipe(
filter(isActionOf(logout.request)),
map(() => login.cancel()),
take(1)
)
)
)
)
Based on how it works, race is actually racing whole first block (so not just outer membershipService.login, with logout.request action. I was not aware of this, and I thought I would need another race condition for membershipService.getUser because what I need is that logout.request terminate whole process (even if login was successful and process in middle of fetching user). As far as I could test this, it's working as expected.
If anyone has any thoughts, improvements, fixes, or anti-pattern observations, please let me know.

Optimistic update strategy using NGRX Effects?

We are integrating the NGRX library in a project at the company where we work and we want to perform optimistic updates at the front-end instead waiting for the server response to perform some action. What we have tried is to use the startWith operator, but it throws the Action properly and then, as the releaseService.deleteRelease method does not return an action, it throws the invalid action: null error.
We have tried to add the {dispatch: false} config to the #Effect, but then the first action is not thrown...
We have also though about using a tap oeprator and dispatch some action directly to the store, but we consider it an anti pattern.
So, is it possible to achieve this without creating an splitter intermediate action? Thanks.
#Effect()
deleteRelease$ = this.actions$.pipe(
ofType(ReleaseCardActions.ReleaseCardActionTypes.DeleteRelease),
exhaustMap((action: ReleaseCardActions.DeleteRelease) => {
return this.releaseService.deleteRelease(action.id).pipe(
startWith(new DeleteReleaseSuccess(action.id)),
catchError(() => of(new ReleasesApiActions.DeleteReleaseFailure()))
);
}),
);
Maybe I don't understand the question, but why not perform the optimistic update on the DeleteRelease action directly in the reducer, so your reducer and effect will fire on the same action independently.
Then, you can do the "real" update from the response coming from the effet.
#Effect()
deleteRelease$ = this.actions$.pipe(
ofType(ReleaseCardActions.ReleaseCardActionTypes.DeleteRelease),
exhaustMap((action: ReleaseCardActions.DeleteRelease) => {
return this.releaseService.deleteRelease(action.id).pipe(
map(new DeleteReleaseSuccess(action.id)),
catchError(() => of(new ReleasesApiActions.DeleteReleaseFailure()))
);
}),
);

Can't get takeUntil to cancel the request

After dispatching SEARCH, I'm trying to cancel an AJAX request but also to keep the observable listening. I'm using takeUntil and waiting for an action of type CLEAR_SEARCH_RESULTS to cancel the request.
Here's my epic:
const searchEpic = action$ =>
action$.ofType(SEARCH).pipe(
debounceTime(500),
switchMap(({ payload }) =>
search(query).pipe(
map(({ response }) => SearchActions.searchSuccess(response)), // dispatches SEARCH_SUCCESS
takeUntil(
action$.ofType(CLEAR_SEARCH_RESULTS)
)
)
)
)
Edit: I have a Redux logger which outputs dispatched actions in the following order:
SEARCH
SEARCH
SEARCH
SEARCH
CLEAR_SEARCH_RESULTS
SEARCH_SUCCESS
(each SEARCH is a keystroke)
I solved it by moving the takeUntil outside of the switchMap and putting a repeat() after it, like so:
const searchEpic = action$ =>
action$.ofType(SEARCH).pipe(
debounceTime(500),
switchMap(({ payload }) =>
search(query).pipe(
map(({ response }) => SearchActions.searchSuccess(response))
)
),
takeUntil(action$.ofType(CLEAR_SEARCH_RESULTS)),
repeat()
)
The problem was that the epic starts listening for CLEAR_SEARCH_RESULTS only after the 500ms of the debounceTime, which is not what I wanted.
All credit goes to this answer by Jay Phelps and to Evert Bouw for finding and pointing it out to me on Gitter.

iOS application closes with no error when trying to retrieve push message data attribute

I'am sending push messages to application with react-native-firebase and it look's wonderful ! Also, I need to receive some portion of data, so sending data something like this
$request_body = [
'to' => $TOKEN_ID,
'notification' => [
'title' => 'Title',
'body' => 'Body',
'sound' => 'default',
],
'data' => [
'key' => 'value',
],
];
I'am trying to listen open push message event as bellow
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen) => {
const notification = notificationOpen.notification;
const data = notificationOpen.data;
setTimeout(() => {
Alert.alert(data.key);
}, 5000);
});
After 5 second left, application closes without any error message. If changing Alert.alert(data.key); with Alert.alert(notification.title); application work`s fine and show an alert. Can someone explain to me, why retrieving data is not work properly?
After some research, I found a solution
const data = notification.data;
or
const data = notificationOpen.notification.data;

During a forkJoin, how can I dispatch an action for each request as well as when they all complete?

I'm using ngrx in an Angular project. In this example I have an array of requests. I want to dispatch an action after each request but also after all are done.
So far I have something looking like this:
Observable.forkJoin(requests).pipe(
map(() => new actions.requestsSuccessful()),
catchError(() => of(new actions.requestsFailed()))
);
where requests is an array of Observables.
The code above works fine, when all requests are done, my requestsSuccessful() action is correctly dispatched.
However, I'm implementing a progressbar, which I want to update after each request has been made, but I also want to keep the dispatch of the action where all requests have been made.
I can't figure out how to dispatch an action after each request while keeping the action when everything is done.
Any ideas?
forkJoin emits only when all Observables complete so it's not useful here. Instead, you can use concatAll and concat.
This is model example simulating what you want if I understand you correctly.
const makeRequest = (v) => of(v)
.pipe(
delay(1000), // Simulate delay
map(response => ({ action: 'WHATEVER', response })), // Map response into action
);
const requests = [makeRequest(1), makeRequest(2), makeRequest(3)];
from(requests)
.pipe(
concatAll(), // Execute Observables in order one at the time
concat(of({ action: 'ALL_DONE' })), // Append this when all source Observables complete
)
.subscribe(console.log);
See live demo (open console): https://stackblitz.com/edit/rxjs6-demo-zyhuag?file=index.ts
This demo will print the following output:
{action: "WHATEVER", response: 1}
{action: "WHATEVER", response: 2}
{action: "WHATEVER", response: 3}
{action: "ALL_DONE"}
Btw, in future RxJS versions there will be endWith operator that you can use instead of concat that makes it more readable. https://github.com/ReactiveX/rxjs/pull/3679
Haven't tested it. Maybe this works.
let progress=0
Observable.forkJoin(requests.map(e=>e.do(()=>progress++)).pipe(
map(() => new actions.requestsSuccessful()),
catchError(() => of(new actions.requestsFailed()))
);

Resources