Thursday, September 21, 2017

How to Import RxJS in Angular Using ES6 Style Imports

Upon building any type of Angular application, you'll eventually need to make a HTTP call and will do this inside of a Provider. The following code illustrates a barebones use of the Http service in Angular for making a call and mapping the response to an Observable.

return this.http.get(apiUrl)
    .map((response:Response) => response.json())
    .catch((error:any) => Observable.throw(error.json().error || 'An error occurred'));

In order to use this code which is based on the ES.Next Observable, you'll need a library like RxJS to use it in your application. Since many IDEs are poor at letting you know exactly which artifacts need to be imported, you'll probably do a Google search and end up with the following:

import 'rxjs/Rx';

The problem with the above is that imports all of RxJS and that is a huge library. The bottom line - don't do that. The proper way is to import the exported Observable, and any operators needed like below:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/toPromise';  //Only needed if wrapping in a Promise to return

However, notice the inconsistency in the import statements. Can't we just import using the following ES6 syntax for everything?

import { member } from "module-name";

In order to explain this, we must understand that there are different ways to import modules using ES6 based on how modules are bundled and exported. To begin let's look at the ES6 documentation. The above method is to import exported members. Well what about my map, catch, etc. RxJS methods? They are not exported from the 'add' directory as they are meant to be patched from that location, so you must import the physical module where they reside using the following syntax:

import 'module-name';

The RxJS documentation eludes to this process, and what we are doing is patching the Observable's prototype under the covers. If you want to see this in action, manually navigate to the map.js file located in the 'rxjs/add/operator/map' directory within 'node_modules' (or via GitHub), and take a look at the code.

Observable.prototype.map = map;

However, if you wish you can still use the following ES6 import as well:

import { map } from "rxjs/operator/map";

I like this for consistency, but there is a difference in the (2) different styles of importing. The module import and patching of Observable is actually better for size-sensitive bundling needs. You just need to make sure you import all needed methods before they are used. Importing and calling the methods directly safeguards against calling methods that haven't been patched, but at the cost of additional code resulting in a larger bundle size.

So fear not, you aren't sloppily mixing implementation styles with your RxJS import statements. Just understand the different styles and stick to one for consistency. There are some additional best practices about importing these modules once per application and enforcing it during linting if you go the route of patching. You can find out more about that process using rxjs-tslint-rules


4 comments:

  1. So what physically does the importing? Guess we find out on Friday ;)

    ReplyDelete
  2. The ECMAScript proposal does include a module loader, but today that functionality must be done via tools like SystemJS, JSPM, or Webpack to import and load (or bundle) modules. All the importing does within a module is allow access to an externally exported module. These tools manage dependency management and determine how to load modules based on dependencies.

    ReplyDelete
  3. I like this explanation better: https://www.learnrxjs.io/concepts/operator-imports.html

    ReplyDelete
  4. Yes that does offer some nice uses cases to explain that I didn't cover. It didn't however cover the points I made about understanding performance with the differing styles of importing.

    ReplyDelete