When should we use the RxJS tap operator? - rxjs

I do not understand from the docs. Could anyone explain it to me?

Most of the operators are working in streamed sequence, for example:
source$.pipe(
map((a: string) => changeAndReturnArray(a)),
filter((b: string[]) => giveMeOnlySymbolsThatAreAfterNInAlphabet(b)),
switchMap((c: string[]) => putToSomeObservable(c))
....
);
In that example you are not 'breaking' the stream, or jumping outside of it to do some external action.
Jumping outside of stream is possible with 'tap' operator, where you can:
call functions that will cause some side effect, that might be visible to end user (for example - display dialog, show snackbar, redirect to different route (but in my opinion it's not recommended to use tap in that way))
dispatch actions for store (if you are using any - for example ngrx store)
debug you're code -> console.log()
anything what can be considered as 'side effect' for your stream.
My personal opinion - use 'tap' only if you can't find any better solution. Jumping outside of stream and calling some side effect can be double edged sword, especially when your dealing with some bigger application. Side effect are always harder to maintain, and you can finish with application that is doing magic stuff without any reason.

You can use it to perform a side effect for example. Or you can use it to see what's the current value that is being passed around without affecting/modifying the Observable. So something like a console.log() but inside the stream.

Decalration
public tap(nextOrObserver: Observer | function, error: function, complete: function): Observable
tap is replacement of do operator which returns observable identical to your source observable. for each value emitted, perform a side-effect. it has 3 optional parameters.
nextOrObserver: A normal Observable object to perform side effect.
error: Callback for errors in source Observable.
complete: Callback for completion of the source.
Recommended for debugging purpose.

Related

Why does assigning an observer to a subject in the subscribe break sometimes?

Imagine the following simple case:
you get some data from a source, resulting in an observable, lets call it obs$
you need to transform it (frex a simple synchronous sort) and hand the result some kind of subject, lets call it subject$$
You now have several ways to implement it. The simplest might look like:
obs$
.pipe(
takeUntil(this.#destroy$$)
)
.subscribe(
result => {
this.subject$$.next(result.dSomething())
});
Works like a charm. A bit later you write some more similar functions, and thus would like to separate the transformation from the assignment. Basically you move the doSomething from the subscribe to a map in the pipe. This could look like:
obs$
.pipe(
takeUntil(this.#destroy$$),
map(result => result.dSomething())
)
.subscribe(res => { this.subject$$.next(res) });
Works fine as well. Though the assignment now looks a bit clunky, surely there is a better way to do this? You might be tempted to go for something like:
obs$
.pipe(
takeUntil(this.#destroy$$),
map(result => result.dSomething())
)
.subscribe(this.subject$$.next);
But quickly find that this fails, as you've got no access to your outer scopes this. The suggested solution looks like this:
obs$
.pipe(
takeUntil(this.#destroy$$),
map(result => result.dSomething())
)
.subscribe(this.subject$$);
Which works just fine in many cases.
Imagine my surprise, when all of a sudden I stumbled into a waterfall of errors:
EmptyError: no elements in sequence
showing up in four completely unrelated places, bubbling up through the code, without an obvious place to even try catch it.
Now following the white rabbit, I searched for far to long until I finally managed to discover the place of origin. Still it doesn't make any sense to me.
Why is the observer "disconnecting" for a moment when there is no activity that could result in a complete? Funnily enough, the subject is catching back up shortly afterwards, so this is not completely breaking the assignment.
Of course, I can always use the slightly more verbose assignment, but understanding why this fails only in some cases would really help avoid those.
Its very hard to suggest anything because I don't know what part of your code and what RxJS chain gives you the EmptyError.
However, I'm suspicious that the problem comes from .subscribe(this.subject$$) because this is not the same as .subscribe(res => this.subject$$.next(res));
All Subjects behave like Observables and observers at the same time. Observers implement this interface that handles all three notification types. If you use .subscribe(this.subject$$) you're passing also error and complete notifications from the source Observable to subject$$ and this is very like your problem. When obs$ completes, the complete notification is passed to subject$$ that completes as well and since it's a Subject it will never ever emit any next again and to all its new observers it'll just pass complete notification and that's it. So this might explain why you're seeing EmptyError somewhere else in you chains that use subject$$.
With .subscribe(res => this.subject$$.next(res)); you're only passing next notifications and complete or error notifications are ignored.

withLatestFrom using two Redux actions

I have been trying to use rxjs properly with Redux observable and Redux actions. I've tried many different combinations of calling the below functions and every possible combination has given me different errors.
Basically, I have two actions that can be dispatched in my app: SET_CIRCULAR_DATA and SET_MODAL_OVER_DEEP_LINK_FLAG.
I'd like another action to be dispatched, determineFoundInProducts() when SET_MODAL_OVER_DEEP_LINK_FLAG happens followed by the first (successful) SET_CIRCULAR_DATA action. Any subsequent SET_CIRCULAR_DATA actions should be ignored until SET_MODAL_OVER_DEEP_LINK_FLAG happens again. Because of this need, I felt withLatestFrom() made the most sense. I also thought that takeUntil() and repeat() might be helpful to help me accomplish what I need, though I'm not including them in this code example because I'm stuck at the withLatestFrom() part.
I've tried different combinations of piping, not piping, using switchMap, map, mapTo, but nothing seems to work. I am importing all the used rxjs functions and I'm combining epics at the root, so it couldn't be that. I just can't seem to get the right combination to not get this to error.
Some examples of errors are
Actions must be plain objects. Use custom middleware for async actions.
You provided 'undefined' where a stream was expected
I am using RXJS ^5.5.6 and Redux Observable ^0.17.0.
import 'rxjs';
import { Observable } from 'rxjs/Observable';
import { withLatestFrom, map, tap, mergeMap } from 'rxjs/operators';
import ....all the types and actions
export const handleProductDetailsModalOnLoginRedirect = (action) =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA).pipe(
withLatestFrom(
action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG)
),
mergeMap(([first, second]) => {
console.log(first, second);
return determineFoundInProducts();
})
);
EDIT
I was struggling in my original post to determine what part of the code was breaking. What I had trouble with was that the redux observable action could not communicate with the rxjs functions. What I found was that piping appropriately piped (pun intended) the action to be workable with rxjs functions. Here is what I did.
action.ofType(navigationTypes.types.INIT_REDIRECT_MODAL_LOGIC)
.switchMap(() =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA)
.withLatestFrom(action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG))
.take(1)
.pipe(
map(([first]) => {
// Do stuff
return determineFoundInProducts();
}))
)
The answer depends on what you want to happen if multiple SET_MODAL_OVER_DEEP_LINK_FLAG actions are dispatched before any SET_CIRCULAR_DATA action is, as well as whether they should some how be paired together with some sort of unique ID or not.
It sounds most likely like you're looking for exhaustMap.
The exhaustMap operator is basically the opposite behavior of switchMap. It maps inputs to inner observable and flattens its output, ignore other input values until that observable completes. It exhausts the inner Observable.
In your use case it means that we first listen for SET_MODAL_OVER_DEEP_LINK_FLAG, then we listen for a single SET_CIRCULAR_DATA, but while we're waiting for that subsequent SET_CIRCULAR_DATA we ignore all possibly incoming SET_MODAL_OVER_DEEP_LINK_FLAG actions. Note that the take(1) is important, because otherwise we'll listen for a stream of every SET_CIRCULAR_DATA, not just one.
export const handleProductDetailsModalOnLoginRedirect = (action) =>
action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG)
.exhaustMap(() =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA)
.take(1)
.map(() => determineFoundInProducts())
);
Btw it's common to ask Redux Observable questions that are actually just RxJS questions. This is a cool thing to note because the RxJS community is massive so you'll find a TON more resources on it, as well as have a better chance of getting help if you're able to rephrase questions to be agnostic of Redux Observable--e.g. change ofType('TYPE') to a filter(d => d.type === 'TYPE')
The above answer will definitely help others. However, what I was struggling with was having redux observable action communicate with RXJS functions. Please see my edit to my original post for solution.

RxJS 5.5.0-beta : mergeMap behavior differing from map + mergeAll

I am having trouble creating a reproducible example so bear with me.
I have a library I created that wraps async API responses in an Observable. Simplifying greatly, the library has functions like lib.ask("end-point") that return an Observable with the response of the API call to that end point.
I want to run that API call every time another event happens. I'm attempting to model that with some version of mergeMap/switchMap/concatMap, depending on the situation. Let's use mergeMap as an example.
If I use map + mergeAll, it works just fine. I am writing it like so:
var responses$ = event$.pipe(
map(()=>lib.ask("end-point")),
mergeAll()
);
When I subscribe to responses$, I get my response!
However, if I rewrite the same chain with mergeMap instead, it does not work:
var responses$ = event$.pipe(
mergeMap(()=>lib.ask("end-point"))
);
In this case, when I subscribe I do not get any results. It almost appears as if the inner subscription never gets made.
Like I said, I am struggling to create a reproducible example. I have attempted to debug what RxJS does to try to find where this thing is failing, but have not been able to figure it out. If anyone can think of why or in what situations mergeMap would behave differently than map + mergeAll, that might help me figure out where my problem is.

How to correctly subscribe to Changed sequence of ReactiveObject?

Another question about ReactiveUi. I have a ViewModel for an edit form. Model is ReactiveObject. I want to enable savecommand only when changes of object was take place. My try:
var canSaveCommand =
this.WhenAnyValue(vm => vm.CurrentClient)
.Where(client => client != null)
.Select(client =>
client.Changed
)
.Any();
But when the form appears the SaveCommand is already enabled. Where my mistake?
You want to use Switch not SelectMany. SelectMany will not unsubscribe from the previous client. It will merge events from all clients. Switch unsubscribes from the previous client before it subscribes to the next.
var canSaveCommand =
this.WhenAnyValue(vm => vm.CurrentClient)
.Where(client => client != null)
.Select(client =>
client.Changed
)
.Switch()
.Any();
For example the following code makes it clear. Let's say we have a class called AudioChannel It generates audio frames we can can process and send to the speaker.
public class IAudioChannel {
public IObservable<AudioFrame> AudioFrameObservable {get;}
}
Then we might have a list of audio nodes that the user can select but we only want the most current sending audio to the speaker. The below class makes available the currently selected audio node as an observable.
public class AudioListViewModel {
public class IObservable<IAudioChannel> CurrentAudioChannelObservable {get;}
}
Now consider the following code
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.SelectMany(current=>current.AudioFrameObservable)
.Subscribe(frame=>frame.Play());
vs
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.Select(current=>current.AudioFrameObservable)
.Switch()
.Subscribe(frame=>frame.Play());
In the first version as we change the selection of audio nodes we add more and more subscriptions. The audio output quickly becomes a garbled mess of mixed channels. In the second version only one channel is subscribed to at a time and the audio output only plays the output from a single channel.
Many people make this mistake when starting out with RX. For example I found a bug in the ReactiveUI framework that used SelectMany instead of Switch.
However
There is a built in way within ReactiveUI to achieve this in a clear way
There is actually another way to achieve what you want and I will put it in another answer just to show you how to use ReactiveUI.
var canSaveCommand =
this
.WhenAnyObservable(vm => vm.CurrentClient.Changed)
.StartWith(false);
Note that null doesn't have to be explicity handled though you should start with false to make sure a value exists when no observable is available to start with.
WhenAnyObservable
WhenAnyObservable acts a lot like the Rx operator CombineLatest, in
that it watches one or multiple observables and allows you to define a
projection based on the latest value from each. WhenAnyObservable
differs from CombineLatest in that its parameters are expressions,
rather than direct references to the target observables. The impact of
this difference is that the watch set up by WhenAnyObservable is not
tied to the specific observable instances present at the time of
subscription. That is, the observable pointed to by the expression can
be replaced later, and the results of the new observable will still be
captured. An example of where this can come in handy is when a view
wants to observe an observable on a viewmodel, but the viewmodel can
be replaced during the view's lifetime. Rather than needing to
resubscribe to the target observable after every change of viewmodel,
you can use WhenAnyObservable to specify the 'path' to watch. This
allows you to use a single subscription in the view, regardless of the
life of the target viewmodel.
Try changing your Select to a SelectMany. That will then give you an Observable of the changes to be passed into Any instead of an Observable of an Observable of the changes to be passed into Any.

Create one-time subscription

I need to create a subscription to an Observable that is immediately disposed of when it is first called.
Is there something like:
observable.subscribeOnce(func);
My use case, I am creating a subscription in an express route handler and the subscription is being called multiple times per request.
Not 100% certain about what you need, but if you only want to observe the first value, then use either first() or take(1):
observable.first().subscribe(func);
note: .take(1) and .first() both unsubscribe automatically when their condition is met
Update from RxJS 5.5+
From comment by Coderer.
import { first } from 'rxjs/operators'
observable
.pipe(first())
.subscribe(func);
Here's why
RxJS has some of the best documentation I've ever come across. Following the bellow link will take you to an exceedingly helpful table mapping use cases to operators. For instance, under the "I want to take the first value" use case are three operators: first, firstOrDefault, and sample.
Note, if an observable sequence completes with no notifications, then the first operator notifies subscribers with an error while the firstOrDefault operator provides a default value to subscribers.
operator use case lookup
UPDATE(DEC/2021):
As toPromise() function has been deprecated in RxJS 7, new functions have been announced to be used instead of it. firstValueFrom and lastValueFrom.
firsValueFrom function resolves the first emitted value and directly unsubscribe from the resource. It rejects with an EmptyError when the Observable completes without emitting any value.
On the other hand, lastValueFrom function is, to a certain degree, same to toPromise() as it resolves the last value emitted when the observable completes. However, if the observable doesn't emit any value, it will reject with an EmptyError. Unlike toPromise() which resolve undefined when no value emits.
For more information, please check the docs.
Old Answer:
If you want to call an Observable only one time, it means you are not going to wait for a stream from it. So using toPromise() instead of subscribe() would be enough in your case as toPromise() doesn't need unsubscription.
To supplement #Brandon's answer, using first() or the like is also essential for updating a BehaviorSubject based on its Observable. For example (untested):
var subject = new BehaviorSubject({1:'apple',2:'banana'});
var observable = subject.asObservable();
observable
.pipe(
first(), // <-- Ensures no stack overflow
flatMap(function(obj) {
obj[3] = 'pear';
return of(obj);
})
)
.subscribe(function(obj) {
subject.next(obj);
});
Clean and Convenient Version
Expanding on M Fuat NUROĞLU's amazing answer on converting the observable to a promise, here's the very convenient version of it.
const value = await observable.toPromise();
console.log(value)
The beauty of this is that we can use that value like a normal variable without introducing another nested block!
This is especially handy when you need to get multiple values from multiple observables. Neat and clean.
const content = await contentObservable.toPromise();
const isAuthenticated = await isAuthenticatedObservable.toPromise();
if(isAuthenticated){
service.foo(content)
}
Of course, you will have to make your containing function async if you are to go with this route. You can also just .then the promise if you don't want the containing function to be async
I'm not sure if there are tradeoffs with this approach, feel free to let me know in the comments so we are aware.
P.S. If you liked this answer, don't forget to upvote M Fuat NUROĞLU's Answer as well :)
I had similar question.
Below was called even later on from different state changers. As I did not want to.
function foo() {
// this was called many times which was not needed
observable.subscribe(func);
changeObservableState("new value");
}
I decided to try unsubscribe() after subscribe as below.
function foo() {
// this was called ONE TIME
observable.subscribe(func).unsubscribe();
changeObservableState("new value");
}
subscribe(func).unsubscribe(); is like subscribeOnce(func).
I hope that helped you too.
observable.pipe(take(1)).subscribe() use take 1 it subscribe for one time then exit

Resources