Is there something like flatTap in RxJS - rxjs

Is there something like flatTap in RxJS? If no, why? Would it be "bad practice"?
Suppose you want to tap something and automatically subscribe (an) inner observable(s) - similar as with flatMap - but you want to keep the same object for further piping. Then I see myself sometimes do something like this:
outerObservable.pipe(
flatMap(v => {
return createInnerObservables(v).pipe(
toArray(),
map(() => v),
);
}),
map(v => do something with v),
)...
With a flatTap operator this could simplify to:
outerObservable.pipe(
flatTap(v => createInnerObservables(v)),
map(v => do something with v),
)...

You can perhaps use a subject to setup the side effect
let doSideEffect=new Subject();
outerObservable.pipe(
tap(v => doSideEffect.next(v)),
map(v => do something with v),
)...
// you can handle when to unsubscribe/complete the stream
doSideEffect.asObservable().pipe(....)

Related

How do I obtain the value of another observable, without it affecting the parent stream's return value

I want to make an http request, without affecting the value of the parent stream:
const mockHttpCall = () => of('http-result');
of('result1')
.pipe(
switchMap(() =>
mockHttpCall().pipe(
tap((innerResult) => console.log('innerResult: ', innerResult))
)
)
)
ie: this stream should return result1 rather than http-result.
How can I do this? I think something might be possible using withLatestFrom and map back to the original value but not sure if it is good idea.
Stackblitz: https://stackblitz.com/edit/rxjs-lrcuse?devtoolsheight=60&file=index.ts
You can just map back to the original value:
of('result1')
.pipe(
switchMap((result) =>
mockHttpCall().pipe(
tap((innerResult) => console.log('innerResult: ', innerResult)),
map(() => result)
)
)
)
.subscribe(console.log)

Pass the value from WithLatestFrom to another operator - Rxjs, Ngrx effect

I've got this ngrx effect and I want to use the data from my state using withLatestFrom.
$loadProduct = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
withLatestFrom(this.store.select(fromSelectors.debounceTime)),
delay(debounceTime), // how can I get the debounceTime value up here?
map(_ => fromActions.foo())
)
)
How can I get this debounceTime value within the scope of delay?
Since delay is just delayWhen(() => timer(duration));, I think you can use delayWhen to solve the problem:
$loadProduct = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
withLatestFrom(this.store.select(fromSelectors.debounceTime)),
delayWhen(([, debounceTimeVal]) => timer(debounceTimeVal)),
map(_ => fromActions.foo())
)
)
As a side note, delayWhen is just an interesting use case of mergeMap: instead of emitting the inner value, the outer value is emitted instead.
How about this:
$loadProduct = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
withLatestFrom(this.store.select(fromSelectors.debounceTime)),
switchMap(([_, debounceTime]) => timer(debounceTime)),
map(_ => fromActions.foo())
)
)
It's using RxJS's switchMap and timer instead of delay.
By the way, if you do not need the action somewhere in the subsequent part of the pipe, you may also use switchMap instead of withLatestFrom like this:
$loadProduct = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
switchMap(() => this.store.select(fromSelectors.debounceTime)),
switchMap(debounceTime => timer(debounceTime)),
map(() => fromActions.foo())
)
)

How to write marble test for this custom RxJS operator used in redux-observalbe Epic

I need to write marble test for my custom operator used in this loadEpic epic - this helps me to avoid problems that action INITIALiZE sometimes is dispatched to late and i getting LOAD_FAILURE:
loadEpic: Epic<ExamManagementAction, ExamManagementAction, RootState> = (
action$,
state$
) =>
action$.pipe(
filter(isActionOf(load)),
waitFor(state$),
switchMap(() =>
this.load(state$).pipe(
map(loadSuccess),
catchError(error => of(loadFailure({ error })))
)
)
);
and this is how i wrote my waitFor operator which works fine:
const waitFor = <T>(
state$: Observable<RootState>
): OperatorFunction<T, T> => source$ =>
source$.pipe(
switchMap(value =>
state$.pipe(
filter(state => state.navigation.initialized),
take(1),
mapTo(value)
)
)
);
can you help me to write this test with rxjs-marbles/jest or any similar approach? many thanks!
You describe three streams of events:
states (mock them with simple objects)
actions (again, you may use any JS value as a mock)
filtered actions (the same object as in 2)
Then you expect your operator to transform 2 to 3 with toBeObservable matcher. That's it.
it('should reject given values until navigation is initialized', () => {
const state$ = hot(' -i--u--u-i-- ', {u: {navigation: {initialized: false}}, i: {navigation: {initialized: true}}});
const action$ = hot(' v----v--v--- ', {v: load});
const expect$ = cold(' -v-------v-- ', {v: load});
expect(action$.pipe(waitFor(state$))).toBeObservable(expect$);
});
Note how I've formatted my code so one stream is described under another. It really helps with long sequences of events.
You might also write separate specs for edge cases. It depends on what behavior you want to test.

Initialize observable with the result of other observable

I have 2 requests.
getCurrentBook(): Observable<Book>
getDetailedInfo(bookId): Observable <BookDetailed>
They both return observables with information, however to use second request I have to make sure that I received the information from the first one since bookId is in the response.
I understand that I could subscribe inside other subscribe, however this solution doesn't seem appealing to me. There must be a much more elegant way.
The existing solution
getCurrentBook().subscribe(res => {
getDetailedInfo(res.id).subscribe(...);
})
I get that it should look something like:
booksSubs = getCurrentBook().pipe(
map(res =>
{this.currentBook = res}
)
)
detailedSubs = getDetailedInfo(this.currentBook.id).pipe(
map(res =>
{this.detailed = res}
)
)
this.subscriptions.push(SOME OPERATOR(booksSubs, detailedSubs).subscribe();
But the option higher won't work since I need result of first observable to initialize second.
You can achieve it using some of "flattening" operators, for example mergeMap:
const currentBookDetails$ = getCurrentBook().pipe(
mergeMap(book => getDetailedInfo(book.id))
);

Returning source observable value after inner observable emits value

Within an observable chain, I need to perform some async work, then return the source value to the next observable so I had to pipe(mapTo(x)) after the async work.
A more complete example:
// fake async work with just 1 null value
someAsyncWork = () => of(null)
of('1', '2', '3').pipe(
// some async work
concatMap(no => someAsyncWork().pipe(mapTo(no))),
concatMap(no => `Some async work [${no}] done!`)
).subscribe(message => console.log(message))
I cannot use tap(no => someAsyncWork()) because that would cause the next observable to run before someAsyncWork() returns.
While my current approach works, it somewhat clutters the code...and I have this pattern repeated in many places within the codebase.
Question: Anyway to do this without pipe(mapTo(no)) - in a more concise/readable way?
Perhaps the simplest thing to do would be to write your own pipeable operator.
For example:
const concatTap = <T>(project: (value: T) => Observable<any>) =>
concatMap((value: T) => project(value).pipe(mapTo(value)));
However, that assumes the observable for the async operation emits only a single value. To guard against multiple values being emitted you could do something like this:
const concatTap = <T>(project: (value: T) => Observable<any>) =>
concatMap((value: T) => concat(project(value).pipe(ignoreElements()), of(value)));
You could use concatTap like this:
of('1', '2', '3').pipe(
concatTap(() => someAsyncWork()),
concatMap(no => `Some async work [${no}] done!`)
).subscribe(message => console.log(message));
I'm sure you could choose a better name than I did. concatTap was the first thing that popped into my head. Naming things is hard.

Resources