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

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())
)
)

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)

Is there something like flatTap in 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(....)

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.

How to preserve a 'complete' event across two RxJS observables?

I have an observable const numbers = from([1,2,3]) which will emit 1, 2, 3, then complete.
I need to map this to another observable e.g. like this:
const mapped = numbers.pipe(
concatMap(number => Observable.create(observer => {
observer.next(number);
}))
);
But now the resulting observable mapped emits 1, 2, 3 but not the complete event.
How can I preserve the complete event in mapped?
Your code gives me just "1" (with RxJS 6); are you sure you see 3 values?
Rx.from([1,2,3]).pipe(
op.concatMap(number => Rx.Observable.create(observer => {
observer.next(number);
}))
).forEach(x => console.log(x)).then(() => console.log('done'))
You're never completing the created Observable (it emits one value but never calls observer.complete()). This works:
Rx.from([1,2,3]).pipe(
op.concatMap(number => Rx.Observable.create(observer => {
observer.next(number); observer.complete();
}))
).forEach(x => console.log(x)).then(() => console.log('done'))
This all shows how hard it is to use Rx.Observable.create() correctly. The point of using Rx is to write your code using higher-level abstractions. A large part of this is preferring to use operators in preference to observers. E.g. in your case (which is admittedly simple):
Rx.from([1,2,3])
.pipe(op.concatMap(number => Rx.of(number)))
.forEach(x => console.log(x)).then(() => console.log('done'))

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