Dispatch an action in response to cancellation - rxjs

I started with the cancellation recipe from the redux-observable docs and want to extend it a bit.
Basically I have a scenario where after the cancellation is triggered, using takeUntil I want dispatch another action to cleanup, etc.
This is what I came up with so far: https://jsbin.com/zemenu/195/edit?js,output
Start a "Fetch User Info" and then hit Cancel. I want it to execute actions in this order:
- USER/FETCH
- REQUEST/STARTED
- USER/CANCELLED
- REQUEST/CANCELLED
This works in the way I have it setup right now. But, I have to rely on passing dispatch into the requestSequence function and then trigger it in finally. Is there a cleaner way to do this just with observable operators? So when that USER.CANCELLED is triggered some final action is mapped to inside the requestSequence observable.
Redux logger is enabled so check the console for all the actions.

Instead of using .takeUntil(), it sounds like you want to use .race(), which is fairly aptly named. Whichever stream emits first, wins! The other is unsubscribed.
You'll need to restructure some things a bit to use it as you want. You want to isolate the first action you emit immediately, your request.onStart(meta), separate from the ajax request Observable.fromPromise(apiCall(...args)). Then you want to race directly between that ajax and the cancellation, so you'd need to pass in the action$ ActionsObservable since you have all this in a helper.
https://jsbin.com/suvaka/edit?js,output
function requestSequence(apiCall, args, meta, action$) {
return Observable.of(request.onStart(meta))
.concat(
Observable.fromPromise(apiCall(...args))
.map((payload) => request.onSuccess(payload, meta))
.catch((e) => Observable.of(request.onError(e, meta)))
.race(
action$.ofType(USER.CANCELLED)
.map(() => request.onCancel(meta))
)
);
}
const fetchUserEpic = (action$, store) =>
action$.ofType(USER.FETCH)
.mergeMap(action =>
requestSequence(
userRequest,
[`/api/users/${action.payload}`],
{ activity: USER.FETCH, path: 'user' },
action$
)
);
Side note: be careful about premature abstractions like making those sorts of helpers. Even though you may repeat things in some epics, I've found abstracting it can make it much harder to grok later, especially if it's someone else who didn't write the code and aren't an Rx guru. Only you can know whether this advice applies to you and your codebase, of course.
The primary confusing point for me is all the arguments you have to pass to requestSequence, which will be tough for many to understand when they first come across it. If you find that very very commonly your epics do exactly the same thing and you want to reuse, perhaps abstracting the entire epic would be more clear, and create API utilities like userRequest below that you can test independently.
(untested, basically pseudo code)
const createApiEpic = options =>
action$ =>
action$.ofType(options.on)
.mergeMap(action =>
Observable.of(request.onStart(meta))
.concat(
options.effect(action.payload)
.map(payload => request.onSuccess(payload, meta))
.catch(e => Observable.of(request.onError(e, meta)))
.race(
action$.ofType(options.cancel)
.map(() => request.onCancel(meta))
)
)
);
const userRequest = id =>
Observable.ajax.getJSON(`/api/users/${id}`);
const fetchUserEpic = createApiEpic({
on: USER.FETCH,
effect: userRequest
cancel: USER.CANCELLED
});

Related

How Subscribe works

I am learning the rxjs and playing with observable and subscribe. I have the following method in component.ts file which returns true/false from API
this.http.get(apiUrl+"/actionName")
.subscribe(result=>
{
if(result){
//step1
//1.show success message
//2.call the other method
//3.and after returned from here
}else{// not true
//1. show error message
//2. returned from here
}
});
});
//step2
// call another function
}
Whenever I subscribe to an observable it immediately jumps to the next line i.e. step 2 and another method gets called first. Which I don't want to do.
I want to run step1 first until it gets finished completely only then it should go to step2.
Thank you in advance.
You don't say so in your question, but I suspect your
//2.call the other method
line contains a nested subscription or a promise. If that's the case, of course your synchronous code will be run before your asynchronous code is run. JavaScript is a single-threaded environment, so you can never wait for other code to run.
Instead, use RxJS various operators to manage the order of your code for you. How you want to do that depends on what you're doing, though sadly call the other method isn't descriptive enough.
Assuming theOtherMethod and anotherFunction are actually strangely named observables, then you might do something like this:
this.http.get(apiUrl+"/actionName").pipe(
switchMap(result => {
if(result){
return theOtherMethod;
}
return of("There's no result")
}),
switchMap(otherMethodResult => anotherFunction)
).subscribe(anotherFunctionResult => {
/* Ignore result?*/
});

NGXS State documentation

I'm new to NGXS and I'm trying to fully understand the docs so I can start using it knowing what I'm doing.
There is one thing I don't understand in this code snippet from here.
export class ZooState {
constructor(private animalService: AnimalService) {}
#Action(FeedAnimals)
feedAnimals(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
return this.animalService.feed(action.animalsToFeed).pipe(tap((animalsToFeedResult) => {
const state = ctx.getState();
ctx.setState({
...state,
feedAnimals: [
...state.feedAnimals,
animalsToFeedResult,
]
});
}));
}
}
Just below this code, it says:
You might notice I returned the Observable and just did a tap. If we
return the Observable, the framework will automatically subscribe to
it for us, so we don't have to deal with that ourselves. Additionally,
if we want the stores dispatch function to be able to complete only
once the operation is completed, we need to return that so it knows
that.
The framework will subscribe to this.animalService.feed, but why?
The action, FeedAnimals, uses the injected service, AnimalService to feed the animals passed in the action's payload. Presumably the service is operates asynchronously and returns an Observable. The value of that Observable is accessed via the tap function and is used to update the ZooState state context based on completing successfully.
In order to use NGXS specifically and Angular in general, you really have to understand RxJS... here's my goto doc page for it

Intercept observables before subscription callback

I am using the following code to make get request:
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url) //code for making get request
return getReqObservable
}
The problem is sometimes my backend might return {error:true, message} with status code 200. (I know thats weird).In that case I want to intecept getReqObservable and not allow its subscription callback to run.
image.component.ts
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url)//code for making get request
return getReqObservable
.do((value)=>{
if(value.error){
//do not allow it to propagate further
})
})
You should propagate it further, but as an error rather than an event (i.e. do just like if your backend did the right thing and returned an error response):
makeGetReq$(url: string): Observable<Something> {
return this.httpClient.get<Something>(url).pipe(
mergeMap(value => value.error ? throwError(value) : of(value))
);
}
Otherwise, the calling method has no way to know that an error occurred, and thus can't execute the callbacks it might have registered for errors.
The easiest would probably be filter.
Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.
It would look like this:
return getReqObservable
.filter(value => !value.error)
It was pointed out, that you lose the notification completely if you just filter out the error case. There is of course the option to create a RxJS error notification with throwError, but it is also possible to just subscribe to the same source observable a second time with a different filter condition.
Be careful however to not call the backend twice, e.g. by using share.

Using NGRX in a Route Resolver

I am using Angular 6.
I am also using NGRX Store.
I am using a route guard to make sure the user is logged in to the application.
I then use a resolver to get the initial user profile, then place it in the NGRX store.
I am new to NGRX and I am not sure if this is the correct way to write the resolver.
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
return this.loginService.getLoginData()
.pipe(
map((result:UserData) => {
this.store.dispatch(new userActions.SetLoginData(result));
this.loginService.getDropdownData(
result.userId,
result.countryCode,
).subscribe( data => {
this.store.dispatch(new userActions.SetDropdownData(data));
})
})
)
}
I am also not sure if this is the correct way to do the RXJS.
any suggestions,
Thanks
I'm going to point you to Preloading ngrx/store with Route Guards, an article of Todd Motto which explains it really well.
There is also an example of the guard in the NgRx example app
#Injectable()
export class CoursesGuard implements CanActivate {
constructor(private store: Store<CoursesState>) {}
getFromStoreOrAPI(): Observable<any> {
return this.store
.select(getCoursesState)
.do((data: any) => {
if (!data.courses.length) {
this.store.dispatch(new Courses.CoursesGet());
}
})
.filter((data: any) => data.courses.length)
.take(1);
}
canActivate(): Observable<boolean> {
return this.getFromStoreOrAPI()
.switchMap(() => of(true))
.catch(() => of(false));
}
}
First of all I think it would make sense to seperate the authentication check and the data resolution into separate classes. For authentication it makes more sense to use a CanActivate guard. See: https://angular.io/api/router/CanActivate
With this out of the way your resolver can focus on only getting the data that is actually required. Here you need to be aware that if you return an observable in your resolver, the observable needs to complete in order for the resolver to complete. The problem is that if you select something from the store, the resulting observable never completes, hence your resolver will never finish resolving your data. You can work around this fact by using the first() or take(1) operator. timdeschryvers answer has a nice example on how to accomplish that.

ReduxObservable cancellation based on action type and its data

I have React app which uses redux-observable with typescript. In this scenario, FetchAttribute Action gets triggered with a id and then make an ajax call.
In certain case, I would want to cancel the ajax request if "FETCH_ATTRIBUTE_CANCEL" action was triggered with the same id as of "FetchAttributeAction" action.
action$.ofType(FETCH_ATTRIBUTE)
.switchMap((request: FetchAttributeAction) => {
return ajax.getJSON(`/api/fetch-attribute?id=${request.id}`)
.flatMap((fetchUrl) => {
// return new action
})
.takeUntil(action$.ofType(FETCH_ATTRIBUTE_CANCEL));
});
interface FetchAttributeAction{
id: number;
}
Problem:
How do we cancel the execution based on action type + action data?
In my case, it would FETCH_ATTRIBUTE_CANCEL and id.
The key is to filter actions in the takeUntil notifier to only those which match the ID you care about.
action$.ofType(FETCH_ATTRIBUTE_CANCEL).filter(action => action.id === request.id)
So here's what it might look like:
Demo: https://stackblitz.com/edit/redux-observable-playground-xztkoo?file=fetchAttribute.js
const fetchAttributeEpic = action$ =>
action$.ofType(FETCH_ATTRIBUTE)
.mergeMap(request =>
ajax.getJSON(`/api/fetch-attribute?id=${request.id}`)
.map(response => fetchAttributeFulfilled(response))
.takeUntil(
action$.ofType(FETCH_ATTRIBUTE_CANCEL).filter(action => action.id === request.id)
)
);
You can also take a look at previous questions:
Redux Observable: If the same action is dispatched multiple times, how do I cancel one of them?
Independent chain cancellation in redux-observable?
Dispatch an action in response to cancellation
The OP also pointed out that they were using switchMap (as did I originally when I copied their code) which would have meant that the epic only ever had one getJSON at a time since switchMap will unsubscribe from previous inner Observables. So that also needed to be chained. Good catch!
I think you should be able to make takeUntil selective for a certain action id with pluck and filter.
ex:
.takeUntil(action%.ofType(FETCH_ATTRIBUTE_CANCEL)
.pluck('id')
.filter((cancelActionID) => cancelActionID === fetchID))
The non-obvious part to me is how to get the current fetchID to run that comparison. I might consider try using do to store in a temporary variable

Resources