Angular NgRx - My Three Pitfalls Using RxJs in Effects

My misunderstandings of RxJs within NgRx lead to some unexpected results

Angular NgRx - My Three Pitfalls Using RxJs in Effects

There exists no better way to learn anything than from experiencing pain. It's a humble yet loud teacher.

Last week, I reviewed code of a project transitioning their state management to NgRx. Looking into the NgRx Effects, I noticed similar pitfalls with RxJs that if not understood well will leave you scrambling for answers. Since my team and I fell victim to these misunderstandings, I hope to alleviate you of these common pitfalls.

Love Web Development?

Angular, Google Cloud, C#, Node, NestJs?

Pitfall #1 - HttpClient Cancelling - switchMap

I trudged through the creation of modeling the state, selectors, reducer, actions, and side effects. Side effects trigger a stream to provide new actions based on external interactions. Web apps communicate with back end services to store state as a source of truth for the different entities within an app. Many of these interactions call REST services.

Side effects occur when an app’s internal state occurs outside of the local environment. In my case, I communicate with a REST service as the source of truth for state.

As I called these REST service from the different actions, I created many effects like below.

This issue reared its ugly head when I dispatched the action above multiple times.

Can you guess what happened?

As I watched my network requests pile in through Chrome's Network tab, the previous requests canceled.

Ughhhh.... what?

switchMap

switchMap cancels the previous observable and subscribes to the new observable. As new FooRequestByIdAction dispatches, the stream cancels the previous HTTP requests.Valid instances arise using switchMap and HTTP requests in effects. For instance, as a user enters their username into a textbox, an HTTP request is made to check the existence of the username. As the user keys in each letter, the previous request may be in flight. In this case, using switchMap cancels the previous HTTP request as the concern is only for the last request.

Solution: mergeMap

mergeMap uses the inner observable projection to the next operator in the effect's stream. It does not cancel the previous observable, allowing our HTTP request to finish, and makes multiple requests at once.

Pitfall #2 - Effect Unsubscribed - catchError

As noted earlier, multiple HTTP requests dispatched for different entities needed by the components. As my project's code base grew for the number of entities, I quickly noticed issues dispatching actions again if the HTTP request failed. While the backend services were developed in parallel, this issue surfaced as REST endpoints changed causing HTTP errors.

Say my component required multiple Foo models, and I dispatched multiple FooActions.GetByIdAction.

Using Redux Dev Tools in Chrome, I found two of the three actions fired and updated my store. The third action dispatched. I received an HTTP request Error, and the FooActions.RequestError dispatched once. I expected the fourth action to dispatch, but I found nothing in my Redux Dev Tool with FooActions.GetByIdAction(4).

I followed a pattern for my effects below of mergeMap of the HTTP response, map success action to save in the store, and catchError in the effect's stream.

Did you catch what will happen if the rest client throws an error?

The effect's catchError returns a new observable of(new FooActions.RequestError(err)). When catchError operator calls, it creates a new observable, and in this case an observable that completes the effect's stream using the of operator and the effect no longer will pick up the action.

Solution: Move the catchError into the inner observable

To fix this, the inner observable on the httpClient handles catching the errors. This observable will emit the correct action based on the response.

Pitfall #3 - Handling User Logouts - takeUntil

I've written a few Firebase applications and enjoyed the real time updates Firestore allows. The rules within Firestore determine how records are queried and written. Below, I created a rule that checks if the current request’s authentication userId matches the userId in the user-profile path in the isUsersDocument function.

Based on the rule, a user may only read and write this record if the isUsersDocument(userId), which checks the current user and userId match.

When a user logged into my system, I wrote an effect that watches for changes in the user-profile collection.

The stream remains open after we dispatch a QUERY_USER_PROFILE action. If the present user were to log out, how do I stop the inner stream from Firebase emitting values? If another person logs in on the same browser, they could view the previous user's information.

My application dispatches a LOGOUT_USER action. I watch for certain actions being dispatched via this.actions$.pipe(ofType(LOGOUT_USER)). So now the question is, what operator do I use within my inner observable that can consume and stop the user-profile value changes stream?

Solution: takeUntil operator watching the action stream

The takeUntil operator consumes the observable provided to it and on the emit of the provided observable stops the outer stream.

Understanding RxJs Operators

RxJs provides a rich library with a steep learning curve. Take a moment to understand which operators change or complete streams. You will save yourself the pain points I’ve experienced.

See Three Patterns for Unsubscribing from Observables in Angular Components.

Changes Streams

  • catchError - From my examples, catchError changes the stream and completes with an of operator.
  • switchMap - cancels previous observable and switches to the new observable

Completes Stream

  • of - sends the values in it's arguments, and completes the stream
  • takeUntil - keeps a stream open until the stream argument emits a value, completing the stream