`ngrx` - how to implement the caching with `effects` - ngrx-store

I like to integrate the cache with my effects. but getting no result. the way i do may not correct.
any one correct me to fix the issue.
here is my code:
constructor(private courseService:CourseService, private actions:Actions,
private store:Store<StateSetupConfig>){}
#Effect()
EffLoadCourse = this.actions.pipe(
ofType(LoadCourse.TYPE),
withLatestFrom(this.store.pipe(select(subscribes.getCourses)),
(action, courses) => {
console.log('courses ::', courses)//getting logged,
return courses
}
),
//but each time backend call initiated!!?
mergeMap((action:LoadCourse) => this.courseService.getCourse().pipe(
map((courses:ModelCourse[]) => (new LoadCourseSuccess(courses))),
catchError(err => of(new LoadCourseFail(err)))
))
)
the problem is, eventhough i back to the current page, I am getting backend call instead of supplying from store. where is wrong? what condition has to be added here?
Thanks in advance.

I come up with following solution: it works for me!!
#Effect()
EffLoadCourse = this.actions.pipe(
ofType(LoadCourse.TYPE),
withLatestFrom(
this.store.pipe(select(subscribes.getCourses)), //getting it from store
(action:LoadCourse, courses: ModelCourse[]) => courses
),
mergeMap((courses:ModelCourse[]) => {
if(courses.length){
return of(courses).pipe(
map((courses:ModelCourse[]) => (new LoadCourseSuccess(courses))),
catchError(err => of(new LoadCourseFail(err)))
)
}
return this.courseService.getCourse().pipe(
map((courses:ModelCourse[]) => (new LoadCourseSuccess(courses))),
catchError(err => of(new LoadCourseFail(err)))
)
})
)

Related

How to simplify nested HTTP calls that use mergeMap

I need to sequentially call multiple HTTP requests. Currently I have this huge epic where I pass all API requests and then try to fire them one by one using mergeMaps. It works, but I believe there must be some easier or cleaner way but. All I found for RxJS was forkJoin but it fires everything in parallel. Am I nitpicking here or is there some much smarter way? Any tips could be helpful here.
Pseudo-code:
All requests are similar and look like this:
import { ajax } from "rxjs/ajax";
import { map } from "rxjs/operators";
export const unpublishAbcApi = (id) =>
ajax({
method: "PATCH",
url: ...,
headers: ..,
}).pipe(
map(({ response }) => ({
published: response.published,
}))
);
And this is my epic
export const deleteEverything = (action$, _,) =>
action$.pipe(
actionOfType(delete.request),
mergeMap(({ payload: { id }}) =>
unpublishAbcApi(id).pipe(
mergeMap(() =>
deleteDefApi(id).pipe(
mergeMap(() =>
deleteGhiApi(otherId).pipe(
mergeMap(() =>
deleteJklApi(id).pipe(
map(() => ({ id }))
)
)
)
)
)
)
);
),
mergeMap(({ id }) =>
// ...get the id and dispatchSuccess etc.
),
catchError(error => /* error handling */)
);
Since the apis are not interdependent on each other, you can just use concat which will execute APIs in sequence!
export const deleteEverything = (action$, _) =>
action$.pipe(
actionOfType(delete.request),
switchMap(({ payload: { id }}) => concat(
unpublishAbcApi(id),
deleteDefApi(id),
deleteGhiApi(otherId),
deleteJklApi(id)
).pipe(map() => ({id}))),
mergeMap(({ id }) =>
// ...get the id and dispatchSuccess etc.
),
catchError(error => /* error handling */)
);

add a timer to an effect dispatch in ngrx

I have the following effect
addItem$ = createEffect(() =>
this.actions$.pipe(
ofType(SkusActions.addItemRequest),
concatMap(({ payload, redirectTo }) =>
this.dataService.addItem(payload).pipe(
map(({ data }) =>
data
? SkusActions.addItemSuccess(payload))
: SkusActions.addItemFailure({})
),
catchError((error) => of(SkusActions.addItemFailure(error)))
)
)
)
);
Since I am using graphql, I have to make sure that the query is succeeded or failed, (the query can go trought but have error in graphql so no data field)
Now, In the case of everything went trought correctly, I would like to add a timer of 3second BEFORE the addItemSuccess fire.
I tried
addItem$ = createEffect(() =>
this.actions$.pipe(
ofType(SkusActions.addItemRequest),
concatMap(({ payload, redirectTo }) =>
this.dataService.addItem(payload).pipe(
map(({ data }) =>
data
? timer(3000).pipe(mapTo(SkusActions.addItemSuccess(payload)))
: SkusActions.addItemFailure({})
),
catchError((error) => of(SkusActions.addItemFailure(error)))
)
)
)
);
but it say the type do not match.
SkusActions.addItemFailure({}) returns an action (javascript object) while timer(3000).pipe(...) returns an Observable.
So you should use switchMap instead (or concatMap or mergeMap would work as well) and always return an Observable:
switchMap(({ data }) => data
? timer(3000).pipe(mapTo(SkusActions.addItemSuccess(payload)))
: of(SkusActions.addItemFailure({}))
),

Removing side effect from observable implementation

I have the following observable implementation
public getFromAddressSubjectObservable() {
let _address: string;
return this.fromAddressSubject.pipe(
tap((address: string) => _address = address),
mergeMap(() => this.rpc.call('', 'smsglocalkeys', [])),
map((keys: SmsgLocalKeysResult) => [...keys.smsg_keys, ...keys.wallet_keys]),
filter((keys: Array<SmsgLocalKey>) => !keys.some(k => k.address === _address)),
mergeMap(() => this.rpc.call('', 'smsggetpubkey', [_address])),
mergeMap((pubkeyData: PublicKeyData) => this.rpc.call('', 'smsgaddaddress', [ pubkeyData.address, pubkeyData.publickey ])),
mergeMap(() => this.rpc.call('', 'smsgaddlocaladdress', [_address]))
)
}
I would like to know if there's a way that I could have this function without side effects, i.e. passing the value of _address from the first operator to the last one of the observable.
One way could be the following. You start defining a method, closureAddressMethod, which expects address as parameter, like this
public closureAddressMethod(address: string) {
return this.rpc.call('', 'smsglocalkeys', [])).pipe(
map((keys: SmsgLocalKeysResult) => [...keys.smsg_keys, ...keys.wallet_keys]),
filter((keys: Array<SmsgLocalKey>) => !keys.some(k => k.address === _address)),
mergeMap(() => this.rpc.call('', 'smsggetpubkey', [_address])),
mergeMap((pubkeyData: PublicKeyData) => this.rpc.call('', 'smsgaddaddress', [ pubkeyData.address, pubkeyData.publickey ])),
mergeMap(() => this.rpc.call('', 'smsgaddlocaladdress', [_address]))
)
}
and then you use this method within the pipe of getFromAddressSubjectObservable method, like this
public getFromAddressSubjectObservable() {
return this.fromAddressSubject.pipe(
mergeMap(address => closureAddressMethod(address))
)
}
Last point, unrelated to your question, is about using mergeMap in a situation which I see as a chain of sequential calls to some remote server. Maybe, in such cases, you may want to consider using concatMap, as suggested in this video from Ben Lesh.

RxJs: Can you spread operators as arguments into pipe operator

I have two observable streams which do very separate mapping logic, but then ultimately end with following 3 operators:
this.selection
.pipe(
..Custom mapping operators
tap(_ => this.devicesLoading = true),
switchMap(d => this.mapService.findLocationForDevices(d)),
map(loc => marker([loc.latitude, loc.longitude])
)
.subscribe(markers => this.plotMarkers(markers));
I want to move the last tap, switchMap, map operators to a common function so I can just apply these within both of my observable streams.
I thought of doing:
private resolveLocationsAndConvertToMarkers = (devices: String[]) => [
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
];
But I wasn't sure how to spread these operators into the pipe arguments, like:#
this.selection
.pipe(
// Custom mapping operators
... this.resolveLocationsAndConvertToMarkers
)
.subscribe(markers => this.plotMarkers(markers));
this errors that there are no overloads that expect 3 or 5 arguments..
You can try use native .apply()
this.selection
.pipe.apply(null,this.resolveLocationsAndConvertToMarkers)
or wrap the list of operator in pipe()
private resolveLocationsAndConvertToMarkers = (devices: String[]) => pipe(
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
);
or return higher order function
private resolveLocationsAndConvertToMarkers = (devices: String[]) => source=>source.pipe(
tap(_ => this.devicesLoading = true),
switchMap((devices: string[]) => this.mapService.findLocationForDevices(devices)),
map(loc => marker([loc.latitude, loc.longitude])
);
You could try a reactive approach (with no side effects unless really isolated):
const preSelection$ = this.selection
.pipe
//..Custom mapping operators
();
const selection$: Observable<Marker> = preSelection$.pipe(
switchMap(preSelection =>
concat(
of(null),
of(preSelection).pipe(
switchMap(d => this.mapService.findLocationForDevices(d)),
map(loc => marker([loc.latitude, loc.longitude]))
)
)
),
shareReplay({ bufferSize: 1, refCount: true })
);
const isLoading$: Observable<boolean> = selection$.pipe(map(x => !!x));
const sideEffectUpdatePlotMarkers$ = selection$.pipe(
tap(markers => this.plotMarkers(markers))
);
// isolate `subscribe` calls and side effects as much as possible
sideEffectUpdatePlotMarkers$.subscribe();
I'm hoping this answer will help anyone else who stumbles across this question. The accepted answer did not exactly work for me, with the primary reason being null was passed as the first parameter for .apply() instead of my observable function again. Here is an example similar to what I successfully implemented in my project.
private pipeActions = [
filter(...),
map(...),
];
private myObservable = combineLatest(...);
doThing(): Observable<any> {
return this.myObservable
.pipe.apply(this.myObservable, [...this.pipeActions]);
}
doOtherThing(): Observable<any> {
return this.myObservable
.pipe.apply(
this.myObservable,
[...this.pipeActions, map(...)], // Do something additionally after my list of pipe actions
);
}

How to dispatch multiple actions from an effect in ngrx conditionally

I am a back-end developer starting with front-end development for a project I am working on. The front-end uses Angular7 and NgRx. I have studied a lot in the last 4 days, but here is something I am stuck with and would appreciate your help.
I learnt that we can dispatch multiple actions from an effect in NgRx by returning an Observable array having multiple actions. I want to dispatch one of the action in the array based on a condition.
My code looks something like this
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap.(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => [
new Action1(),
new Action2(),
]),
catchError(error handling)
);
}),
);
and I want to achieve something like this
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap.(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => [
if(condition)
new Action1()
else
new Action1.1() ,
new Action2(),
]),
catchError(error handling)
);
}),
);
I think its my lack of knowledge of RxJs, which is preventing me to implement the condition.
You can dispatch multiple actions or specific actions by letting conditional ifs determine what iterable to return
I recommend you read: https://www.learnrxjs.io/operators/transformation/switchmap.html
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => {
let actionsToDispatch = [];
if(condition) {
actionsToDispatch.push(new SomeAction())
} else {
actionsToDispatch.push(new SomeOtherAction())
}
return actionsToDispatch
}),
catchError(error handling)
);
}),
);
To dispatch multiple actions you can pass the action array as shown below:
#Effect()
getTodos$ = this.actions$.ofType(todoActions.LOAD_TODOS).pipe(
switchMap(() => {
return this.todoService
.getTodos()
.pipe(
switchMap(todos => [
new todoActions.LoadTodosSuccess(todos),
new todoActions.ShowAnimation()
]),
catchError(error => of(new todoActions.LoadTodosFail(error)))
);
})
);
To dispatch actions conditionally you can wrap the actions in if/else as shown below:
#Effect()
getTodos$ = this.actions$.ofType(todoActions.LOAD_TODOS).pipe(
switchMap(() => {
return this.todoService
.getTodos()
.pipe(
switchMap(todos => {
if(true) {
return new todoActions.LoadTodosSuccess(todos),
} else {
return new todoActions.ShowAnimation()
}),
catchError(error => of(new todoActions.LoadTodosFail(error)))
);
})
);
Hope that helps!

Resources