Optimisation of RXJS chain - rxjs

There has to be a better way to do this, right?
const source$ = combineLatest([
folder$.pipe(
filter(folder => folder.canBeLoaded()),
),
page$,
sort$,
]).pipe(
takeUntil(this.onceDestroyed$),
switchMap(([folder, page, sort]) => combineLatest([
of(folder),
of(page),
of(sort),
// buffer$: BehaviourSubjhect<number>
buffer$.pipe(
startWith(-1),
pairwise(),
filter(([buffered, wanted]) => wanted > buffered ),
map(([, current]) => current),
distinctUntilChanged(),
),
])),
);
withLatestFrom comes to my mind, but it will return once per parent, I need buffer$ to be able to emit multiple times!

no need to be that complicated, below should work upper/lower stream are always the latest.
const source$ = combineLatest([
folder$.pipe(
filter(folder => folder.canBeLoaded()),
),
page$,
sort$,
]).pipe(
takeUntil(this.onceDestroyed$),
switchMap(([folder, page, sort]) =>
buffer$.pipe(
startWith(-1),
pairwise(),
filter(([buffered, wanted]) => wanted > buffered ),
map(([, current]) => current),
distinctUntilChanged(),
map(current => [folder, page, sort,current]),
),
),
);

Related

rxjs/redux-observable dynamic filtering based on different payloads

In short, I would like to apply a switchMap to observables depending on their payload.
I currently have an epic with redux-observable that performs GETs on /api/document/{name}. I include name in the payload of my actions. The challenge is that I only filter based on the action type, and so I don't know how to filter dynamically based on the action's payload too.
const documentFetchEpic: Epic<TRootAction, TRootAction, TRootState> = (action$, store) =>
action$.pipe(
filter(isActionOf(documentActions.fetch)),
// TODO: Should filter and then switchMap based on different documentName
mergeMap(action =>
merge(
ApiUtils.documents.fetch(action.payload).pipe(
mergeMap(response => [
documentActions.fetchSuccess({
name: action.payload,
data: response.data,
}),
]),
catchError(err => of(documentActions.fetchFailure(err)))
)
)
)
);
If I made this mergeMap a switchMap, it would apply a switchMap independent of the action value. Instead, I would just like to use a switchMap if action.payload is the same.
Discovered the groupBy command and came up with this... does this seem reasonable?
const documentFetchEpic: Epic<TRootAction, TRootAction, TRootState> = (action$, store) =>
action$.pipe(
filter(isActionOf(documentActions.fetch)),
groupBy(({ payload }) => payload),
mergeMap(group =>
group.pipe(
switchMap(action =>
merge(
ApiUtils.documents.fetch(action.payload).pipe(
mergeMap(response => [
documentActions.fetchSuccess({
name: action.payload,
data: response.data,
}),
]),
catchError(err => of(documentActions.fetchFailure(err)))
)
)
)
)
)
);

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
);
}

catching error of multiple http requests using concat

Able to catch error inside individual ajax requests using pipe operator, but how can i catch outside concat. getting error source.lift not a function. please point out where i am going wrong and what would be better option for serial requests.
export const deleteSettingsEpic = action$ => action$.pipe(
ofType('DELETE_SETTINGSDATA'),
flatMap(action$ => concat(
ajax.ajaxDelete(`${action$.payload.api}/${action$.payload.id}`)
.pipe(
map(r => ({ type: 'DELETE_SUCCESS' })),
// catchError( e => of({type: 'DELETE_ERROR'}))
),
ajax.get(`${action$.payload.api}`)
.pipe(
map(r => ({
type: action$.payload.getPaginationAction,
payload: {
leadSourceList: 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'})
),
catchError(e => of({type: 'FETCH_ERROR'}))
),
);

duplicate action on stream redux observable

I got duplicate action firing when I subscribe to a socket io event.
const onStartGameActionEpic = (action$, state$) =>
action$.pipe(
ofType(ON_START_GAME),
mergeMap(() =>
fromEvent(socket, 'newGameCreated').pipe(
map(response => onStartGameFulfilled(response)),
),
),
);
You're creating a new listener every time ON_START_GAMEoccurs, but you're never killing off the old ones. That's the root of the issue.
Fix 1
Change mergeMap to switchMap.
Fix 2
You might want to have multiple games going at once. If so, assign a namespace prop to your ON_START_GAME action. When the game end action fires, kill off that specific observable.
const onStartGameActionEpic = (action$, state$) =>
action$.pipe(
ofType(ON_START_GAME),
mergeMap(onStartGameAction =>
fromEvent(socket, 'newGameCreated').pipe(
takeUntil(action$.pipe(
ofType(ON_END_GAME),
filter(onEndGameAction => (
endGameAction.namespace === startGameAction.namespace
)),
)),
map(response => onStartGameFulfilled(response)),
),
),
);

Test debounce with marble diagram

I have epic that debounce until it successfully call ping api. I tested successfully ping case. I'd like to use import { TestScheduler } from 'rxjs/testing'; and test when I cannot ping to 1st call, but success in 2nd with marble diagram. is there any example?
export const resendData = (action$, store$, {service}) => {
return action$
.pipe(
ofType(
SOMETHING_FALL
),
debounce(() => service.get('/api/ping').pipe(
retryWhen(err =>
err.pipe(
scan((acc, _) => acc * 2, 2),
delayWhen(val => timer(val * 1000))
)
),
catchError(_ => empty())),
),
map(_ => retryAction()),
);
}

Resources