Moving from chaining to piping in rxjs 6.x

Moving from chaining to piping in rxjs 6.x

Last month I updated all my NPM packages and realised that everything was broken! The reason why was that rxjs decided to move out of extensions on observable in favor of pipable functions from 5.x to 6.x. A major breaking change where majority if not all codebase built using rxjs needs to be changed.
Today we will explore the reasoning behind this change.

  1. Reasons
  2. Example of migration
  3. Learnings

1. Reasons

The reasons given on the official documentation are:

  1. Any library importing the patch operator augmenting the observable prototype would also augment it for all consumers. For example, when a library imports map extension from import 'rxjs/add/operators/map', it makes map available for all code importing the library itself. This could bring confusion due to the inconsistent way of importing extensions. Some places would not need to important map (those were the library is used) while in other place of the code we would have to else the compiler would complain. The other issue would be that if we did not import the extension and relied (intentionally or not) on the import of the library we consume, the day they remove the import, our application will break.
  2. This point is more on the optimization side. Unused operators added on prototype can’t be removed during tree-shaking tools like webpack.
  3. Operators patches imported but not used can’t be deleted by linters.

2. Example of migration

Moving to pipe, import the operarors. For example this is a common code in my ngrx effects:

@Effect()
load$: Observable<Action> = this.actions$
  .ofType(account.LOAD_ALL)
  .switchMap(() => {
    return this.service.get()
      .map(accounts => new account.LoadAllSuccessAction(accounts))
      .catch(() => of(new account.LoadAllFailAction()));
  });

If you are interested in ngrx checkout my previous blog post How to get started with Redux in Angular with ngrx

Now we can use the pipe function

@Effect()
load$: Observable<Action> = this.actions$
  .ofType(account.LOAD_ALL)
  .pipe(
    switchMap(() => {
      return this.service.get().pipe(
          map(accounts => new account.LoadAllSuccessAction(accounts)),
          catchError(() => of(new account.LoadAllFailAction()))
      );
    })
  );

The rest of the operators now become simple standalone functions. We can combine them by passing them in pipe. This move brings us closer to a functional programming style as operators then become building blocks for us to build operators fulfilling our business.
We could have done it before too but it wasn’t as straight forward. Here we only have a simple dedicated function.

Moving out of chaining, some functions made more sense as first class like combineLatest.
Instead of chaining it, we can directly use:

combineLatest(obs1$, obs2$)

This make more sense than chaining as obs1 and obs2 are equal in term of behavior, unlike withLatestFrom(obs2) which only cares about the last value of obs2.

combine(obs1$, obs2$)
  .pipe(
    map(...),
    withLatestFrom(obs3$),
    map(...)
  )
  .subscribe(...);

Lastly due to the fact that the functions are no longer extensions, some of them collide with keywords of Typescript.

For example, .do and .catch are reserved keywords. do has been changed to tap.

obs$.pipe(tap(x => console.log(x)));

Another one is catch which has been renamed to catchError.

Those were the changes I had to make in my oen application, there might be more to change in your own project. The list can be found on the rxjs github repository.

3. Learnings

When frameworks like rxjs switch directions, it is always interesting to try to understand the thought process behind it. I personally believe that frameworks of this caliber used by many applications bring changes to fulfill a particular vision. A vision which will enhance the way we program. Therefore deducing learnings is always good. From this change, I can see three points.

  1. Pipes are awesome
  2. We must strive to reduce codebase
  3. We must try to understand as much as we can the framework used

Let’s talk more about each points in order:

Piping offers more possibilities than chaining as it allows composition of functions and extending the functionalities is less painful as augmenting Observables. We also get away from the dependency on the observable object and the function becomes a “first citizen”.

Keep codebase as tiny as possible. Trim and delete outdated code. Things are likely to change and code that you don’t own is likely to break. Keeping the codebade small will allow us to be quicker in refactoring.

Lastly understanding the scementic of the framework and ideology behind it allows us to understand the changes quicker as we know which function replaces which and we know what to expect in term of result.

Conclusion

Today we saw how to migrate from rxjs 5 to 6. The major change being moving toward pipes. We saw the reasons why the rxjs team decided to move toward pipes, we then saw some example of refactoring from chaining to pipe and lastly we extracted learnings from this exercise. Hope you like this post, see you next time!

Comments

Popular posts from this blog

A complete SignalR with ASP Net Core example with WSS, Authentication, Nginx

Microsoft Orleans logs warnings and errors

One way to structure Web App built in F# and WebSharper