3 reasons to use the tap operator from RxJs

Alain Chautard
Angular Training
Published in
3 min readOct 14, 2021

--

The tap operator is one of my favorite and most used features of RxJs. So today, I’m going to highlight three different and handy use cases of the tap operator.

Debugging by logging what is going on in a data stream

This is one of the easiest ways to visualize what is happening within a pipe of observable streams. tap can be used to spy on a stream without changing the behavior of said stream:

observable.pipe(
map(post => post.email),
tap(console.log),
filter(email => email.startsWith("A")),
tap(data => console.log(data))
)

In the above example, tap operators log the data “as-is” in the console, which is a perfect way to make sure that the data is filtered and transformed as expected along the way.

Note that tap(console.log) is a shorter version of tap(data => console.log(data)). Both versions work the same way.

Define side effects as data changes

Developers often subscribe too early to observables because they need some data at a specific point in time, so they end up subscribing at that point to receive the data.

The tap operator can fix that by registering a side-effect on your stream, which is a fancy way to say, “I want to run some code whenever the data changes”.

A best practice is to rely on the async pipe to subscribe and unsubscribe from our Observables automatically. With the tap operator, we can implement that best practice while accessing the data in “other places” as well.

Let’s take the example of a LoginService that has a login method. The login method makes an HTTP request to receive an authentication token from a server. If we need to store the current user name and token in that service, it would be tempting to subscribe to the HTTP request’s Observable, but that would prevent us from starting the subscription from the calling code.

Here is how to use tap to prevent subscribing too early:

login(username, password): Observable<string> {
return this.http.put('http://server.com/login', {username, password})
.pipe(
tap(data => {
this.currentUser = username;
this.isLoggedIn = true;
this.authToken = data['token'];
}),

map(tokenObj => tokenObj['token'])
);
}

In this example, the login method returns an Observable, doesn’t subscribe, and still manages to register a side-effect with tap that will catch any username/token change to update its internal state.

Furthermore, the calling component can also register its side-effects if needed, using the pipe method to keep chaining more operators:

this.loginService.login(username, password).pipe(
tap(token => this.token = token)
);

Watch for completion of inner observables

tap can be convenient to listen to completion in an inner Observable as well. This can be achieved with the following syntax:

observable.pipe(
concatMap(n => otherObservable.pipe(
take(Math.round(Math.random() * 10)),
map((data) => data.done),
tap({
complete: () => console.log(`Done with ${n}`)
})

))
)

Note that you can register the usual three callbacks in the tap operator using that syntax. However, all three properties/callbacks are optional:

tap({
next: (data) => // This will run when data is received,
complete: () => // This will run on Observable completion,
error: (error) => // This will run on Observable error,
})

If you’re using tap in a creative manner, please let me know in the comments. It’s always impressive to see what can be achieved with simple RxJs operators.

My name is Alain Chautard. I am a Google Developer Expert in Angular, as well as a consultant and trainer at Angular Training where I help web development teams learn and become comfortable with Angular.

If you need any help with web development, feel free to get in touch!

If you enjoyed this Angular tutorial, please clap for it or share it. Your help is always appreciated. You can also subscribe to Medium here.

--

--