Independent chain cancellation in redux-observable? - javascript

I'm new to RxJS. In my app I need independent cancellation of deferred action. Here's a working example (the delay is 3 seconds). But when I choose to delete multiple items and cancel one of them, then canceled all at once.
Epic code:
const itemsEpic = action$ =>
action$.ofType('WILL_DELETE')
.flatMap(action =>
Observable.of({type: 'DELETE', id: action.id})
.delay(3000)
.takeUntil(action$.ofType('UNDO_DELETE'))
)
I think I need to pass an id to takeUntil operator, but I don't know how to do it.

If I understand the takeUntil operator correctly, it stops emitting new items from the Observable it was called on, once the argument Observable emits it's first item. With this in mind you could do something like this:
const itemsEpic = action$ => action$.ofType('WILL_DELETE')
.flatMap(action => Observable.of({ type: 'DELETE', id: action.id })
.delay(3000)
.takeUntil(action$.ofType('UNDO_DELETE').filter(({id}) => id === action.id))
)

Related

Reactive Loading state management with RxJs

A classic task when you have some input field and you have to fetch something on values changes. Let's imagine we use Angular Reactive Forms. Example:
orders$ = inputControl.valueChanges.pipe(
switchMap((value) => {
return someService.fetch(value);
})
);
Now we should also somehow manage loading state. I usually use tap:
orders$ = inputControl.valueChanges.pipe(
tap(() => { loading = true }), // or loading$.next(true) if loading is a subject
switchMap((value) => {
return someService.fetch(value);
}),
tap(() => { loading = false }), // or loading$.next(false) if loading is a subject
);
However it seems we can somehow avoid assigning values in the tap and use RxJs instead.
But I could not find a way to do with it.
For me, the usage of the ideal solution would be
orders$ = <some abstraction here that depends on inputControl.valueChanges and fetching>
loading$ = <some abstraction here that depends on fetching>
Take a look at my library ez-state.
https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ez-state
https://adrianbrand.medium.com/angular-state-management-using-services-built-with-ez-state-9b23f16fb5ae
orderCache = new EzCache<Order[]>();
order$ = this.orderCache.value$;
loading$ = this.orderCache.loading$;
inputControl.valueChanges.subscribe(value => {
orderCache.load(someService.fetch(value));
});
An EzCache is a glorified behaviour subject that has methods load, save, update and delete and manages state observables like loading$, loaded$, saving$, saved$ etc.
I would personally would have the cache in the service and expose the observables from the service like in that article I wrote.
If you think about the loading state as part of a larger "order search request", then it becomes easier to see how to use RxJS to create an observable that emits the desired overall state.
Instead of having two separate observables, orders$ and loading$, you could have a single observable that emits both pieces of data (since they are always changed at the same time).
We basically want to create an observable that initially emits:
{
isLoading: true,
results: undefined
}
then, after the results are received, emits:
{
isLoading: false,
results: ** orders from api call **
}
We can achieve by using switchMap, map, and startWith:
orderSearchRequest$ = this.searchTerm$.pipe(
switchMap(term => this.searchService.search(term).pipe(
map(results => ({ isLoading: false, results })),
startWith({ isLoading: true, results: undefined })
)),
);
<form [formGroup]="form">
<input formControlName="searchTerm" placeholder="Order Search">
</form>
<div *ngIf="orderSearchRequest$ | async as request">
<div *ngIf="request.isLoading"> loading... </div>
<ul>
<li *ngFor="let order of request.results">
{{ order.description }}
</li>
</ul>
</div>
Here's a working StackBlitz demo.

Combine multiple observables in to a single RxJS stream

It appears I am lacking knowledge on which RxJS operator to resolve the following problem:
In my music application, I have a submission page (this is like a music album). To load the submission, I use the following query:
this.submissionId = parseInt(params['album']);
if (this.submissionId) {
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.subscribe((submission) => {
//submission loaded here!
});
}
Easy enough! However, once I've loaded the submission, I have to load some auxiliary information such as the current user (to check if they are the artist of the submission) and comments. In order to avoid nested subscriptions, I can modify the above query to use switchMap to switch the query stream to user and comments observables once the submission resolves:
// stream to query for the submission and then switch query to user
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
return this.auth.user$
})
).subscribe((user) => {
// needs value of submission here
if (user.id == this.submission.user.id) {
//user is owner of submission
}
})
// stream to query for the submission and then switch query to comments
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id //needs submission response here
})
return this.comments$.valueChanges
})
).subscribe((comments) => {
this.comments = comments;
})
Great! I've avoided the nested subscription issue BUT now...the first part of each submission request is identical. Basically, once, the submission is queried, i want to launch off two parallel queries:
a query for the user
a query for the comments
Which RxJS operator can perform such an operation? I suppose the subscribe at the end would emit an array response like:
.subscribe([user, comments] => {
// check if user == submission.user.id here
// also assign comments to component variable here
})
I believe mergeMap is sort of what I need but I'm not sure how to implement that properly. Or is this a case where I should share() the submission query and then build off my parallel queries separately? I'm very curious! Please let me know, thanks!
You can use the RxJS forkJoin operator for this scenario. As stated on the documentation,
When all observables complete, emit the last emitted value from each.
const userQuery$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
return this.auth.user$
})
)
// stream to query for the submission and then switch query to comments
const commentsQuery$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id //needs submission response here
})
return this.comments$.valueChanges
})
)
forkJoin(userQuery$, commentsQuery$).subscribe([user, comments] => {
// check if user == submission.user.id here
// also assign comments to component variable here
})
Try:
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
const user$ = this.auth.user$;
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id
});
return combineLatest(user$, this.comments$);
}),
// maybe put a takeUntil to remove subscription and not cause memory leaks
).subscribe(([user, comments]) => {
// check if user == submission.user.id here
// also assign comments to component variable here
});
Something you should consider is eliminating instance variables with the help of the async pipe given by Angular (https://malcoded.com/posts/angular-async-pipe/).
It will subscribe to the observable, present it into the view and automatically unsubscribe when the view is destroyed.
So, using that, we can get rid of this.submissions = submission by putting:
submissions$: Observable<ISubmission>; // assuming there is an interface of ISubmission, if not put any
// then when this.submissionId is defined
this.submissions$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges;
// then when using it in your view you can do {{ this.submissions$ | async }}
The same thing can go for this.comments$. All of this is optional though. I try to minimize instance variables as much as possible when using RxJS because too many instance variables leads to confusion.
Then you can lead off of this.submissions$ observable and subscribe for the other main stream.
this.submission$.pipe(
switchMap(submission => ..... // everything else being the same
)
I chose the combineLatest operator but you can use zip and forkJoin as you see fit. They all have subtle differences (https://scotch.io/tutorials/rxjs-operators-for-dummies-forkjoin-zip-combinelatest-withlatestfrom).

What's the proper way to dispatch action inside epic?

What is the proper way to dispatch operationReset() inside of redux-observable epic?
Should I import actual store and use it?
It used to be like this, but following store is deprecated, and will be removed
// show operation failed message
(action$, store) => action$.ofType(OPERATION_FAILURE).map(() => (error({
title: 'Operation Failed',
message: 'Opps! It didn\'t go through.',
action: {
label: 'Try Again',
autoDismiss: 0,
callback: () => store.dispatch(operationReset())
}
}))),
This probably raises a larger question about how one should do notifications with callbacks, since it means you're sending a non-JSON serializable function as part of an action.
I'll assume you want to match the react notification system still. There's a way you can do this using Observable.create:
(action$, store) =>
action$.pipe(
ofType(OPERATION_FAILURE),
mergeMap(() =>
Observable.create(observer => {
observer.next(
error({
title: "Operation Failed",
message: "Oops! It didn't go through.",
action: {
label: "Try Again",
autoDismiss: 0,
callback: () => {
// Send off a reset action
observer.next(operationReset());
// Close off this observable
observer.complete();
},
// If the notification is dismissed separately (can they click an x?)
onRemove: () => observer.complete()
}
})
);
})
)
);
NOTE: I still wouldn't want to send callbacks as part of actions. Amusingly, one of my projects uses that notification system component too -- we have epics that will add notifications and clear them based on actions. All actions stay pure, and the notification system is a controlled side effect.

Dispatching a delayed second action with Redux-Observable while keeping the first one

I'm trying to create an epic that will take an action, and then dispatch two different actions, with the second one delayed by two seconds. After a bunch of attempts, this was the best I could do:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
.merge(Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000))
Unfortunately, it seems that:
Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000)
Is run immediately upon my app being loaded (rather than when an event comes down the parent stream). I noticed this because the FETCH_SUCCESS action is received by the reducer exactly two seconds after my app is loaded. I even attached a console.log to confirm this:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
.merge(Observable.of({ type: 'FETCH_SUCCESS' })
.do(() => console.log('this has begun'))
.delay(2000)
)
"this has begun" is logged to the console the moment the app is started.
I suspect this has something to do with how Redux-Observable automatically subscribes for you.
The desired behaviour is that I will:
Click a button that dispatches the FETCH_WILL_SUCCEED event.
Immediately, a FETCH_REQUEST event is dispatched.
Two seconds after that, a FETCH_SUCCESS event is dispatched.
It turns out I needed to wrap both of my events inside a mergeMap. Thanks to #dorus on the RxJS Gitter channel for this answer.
This is my working result:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mergeMapTo(Observable.of({ type: 'FETCH_REQUEST' })
.concat(Observable.of({ type: 'FETCH_SUCCESS' })
.delay(1000)))
merge should work as well in place of concat, but I thought concat makes better semantic sense.
There might be a more elegant solution, but why not just use 2 epics and combine them?
The first one dispatches the fetch request:
const onFetchWillSucceed = action$ => action$.ofType('FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
The second one waits 2 secs and dispatches the success:
const onFetchRequest = action$ => action$.ofType('FETCH_REQUEST')
.delay(2000)
.mapTo({ type: 'FETCH_SUCCESS' })
And in the end they are just combined into 1 epic
const both = combineEpics(onFetchWillSucceed, onFetchRequest)

Population with embeded queries RxJs

I cannot figure out how can I solve the following problem.
There is an object type:
Box {
Fruit[n]: {
Kinds[n]: {
id: string;
name: string;
}
}
}
I got the box of fuits from an API call as an Observable (Angular2) [Fruit[]] then I want to populate its "navigation property" with another API call what gives back an observable as well like:
Box.foreach(fruits =>
fruits.foreach(f =>
f.kinds.foreach(k =>
k.name = kindservice.getKindName(k.id) // <- observer
)))
How can I do it with RxJs?
I tried many ways, there are many mapper but I could not figure out yet.
I used the Observable.from(..) as well but there was no luck.
Thank you
Your most inner loop has 2 problems:
You assign an observer to a value.
Your observer stays cold.
Try changing it to:
Box.foreach(fruits =>
fruits.foreach(f =>
f.kinds.foreach(k =>
kindservice.getKindName(k.id).subscribe(name => k.name = name)
)))

Categories