Dealing with Side Effects
In this lesson, we are going to look at a rather interesting case that we will
bump into in our next application build and how it interacts with connect.
This is something that is going to generally occur when the purpose of
a subscription is essentially to just trigger something else.
See if you notice anything weird about this connect set up:
constructor() { // reducers const nextState$ = merge( this.messages$.pipe(map((messages) => ({ messages }))), this.logout$.pipe(map(() => ({ messages: [] }))), this.error$.pipe(map((error) => ({ error }))), this.add$.pipe( exhaustMap((message) => this.addMessage(message)), ignoreElements(), catchError((error) => of({ error })) ) );
connect(this.state).with(nextState$); }Pretty standard… except for this:
this.add$.pipe( exhaustMap((message) => this.addMessage(message)), ignoreElements(), catchError((error) => of({ error })) )For context, this is what this would have looked like if we were not using
connect:
this.add$ .pipe( takeUntilDestroyed(), exhaustMap((message) => this.addMessage(message)) ) .subscribe({ error: (err) => { console.log(err); this.error$.next('Failed to send message'); }, });Notice that we aren’t actually using the value from the add$ source inside of
the subscribe. The only reason we are subscribing to it is because we want to
trigger the addMessage method, which we are switching to. We do want to handle
any errors that occur though.
So, we need to subscribe to trigger this method with the add$ value, we
don’t want that value to be set in our state, but we do want to handle any
potential errors and set that into our state.
That is why we do this:
this.add$.pipe( exhaustMap((message) => this.addMessage(message)), ignoreElements(), catchError((error) => of({ error })) )We of course trigger the addMessage method, but then we have this
ignoreElements operator. This is one of those lesser used operators, but very
useful here. This will simply ignore any data emissions on the stream — we won’t
get our normal next values, but we will still get our error values.
Now if we use connect to subscribe to our add$ source we don’t need to worry
about its values attempting to be set in the state and causing problems because
we just ignore any emissions.
The ignoreElements won’t stop the errors though, and we don’t want it to.
Instead, we catch the error and for our replacement stream we return the value
of the error as an observable in the format that our state signal expects, e.g:
{ error: 'some kind of error'}So, in the event that an error occurs, now the stream won’t actually error
because we catch it, it will just emit the value of the error on the stream as
if it were a normal next value (and this one won’t be prevented by our
ignoreElements because that is further up the chain).
This is a bit of a creative solution that does require some knowledge of more obscure RxJS techniques. Situations like this are more rare, but you might run into stuff like this. As always, try to solve these in the “reactive/declarative” way, but ultimately you can just work around it some other way if you need to.
There is no law preventing us from using a standard subscribe call alongside
a connect function. The more you run into these tricky situations the more
experience you will get, and before you know it you’ll be slinging operators
around like they are nothing.