ncjamieson

Managing RxJS Imports with TSLint

July 21, 2017 • 4 minute read

Jigsaw puzzle

There are a number of options for importing RxJS and these are detailed in the documentation.

You can import everything:

import Rx from "rxjs/Rx";

Rx.Observable.of(1, 2, 3).map(i => i.toString());

You can import only the methods you need, patching the Observable prototype:

import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
import "rxjs/add/operator/map";

Observable.of(1, 2, 3).map(i => i.toString());

Or you can import the methods to be called directly — not via the Observable prototype:

import { Observable } from "rxjs/Observable";
import { of } from "rxjs/observable/of";
import { map } from "rxjs/operator/map";

const source = of(1, 2, 3);
const mapped = map.call(source, i => i.toString());

Each of these options has its advantages and disadvantages:

  • importing everything is simple and ensures all methods are available, but comes at the cost of importing the entire 586 KB (unminified) bundle;
  • patching Observable with only the methods you wish to use results in a smaller build, but care must be taken to ensure that methods are imported before they are used and that all necessary methods are imported;
  • importing and calling methods directly avoids the possibility of calling a method that has not been patched into the Observable prototype, but comes at the cost of more verbose code and lost type information (as, at this point in time, Function.prototype.call returns any).

It makes sense to adopt one of the above options as a general policy for a code base. The enforcement of such a policy can be managed using TSLint and some custom rules.

Rules

I’ve compiled a small set of custom, import-related rules; they are included in the rxjs-tslint-rules package.

The rules are fine-grained and can be combined in various ways to enforce numerous RxJS import polices.

Rules for the Angular Policy

The Angular approach is to import methods directly and to use call to invoke them on observable instances. One of the motivations for this approach is to avoid breaking client code between Angular updates. If Angular were to patch the Observable prototype and if clients were to rely upon methods being present solely due to Angular’s patching of the prototype, client code would break if Angular ceased to patch a particular method.

The rule combination looks like this:

"rxjs-no-add": { "severity": "error" },
"rxjs-no-patched": { "severity": "error" },
"rxjs-no-wholesale": { "severity": "error" }

rxjs-no-add disallows imports that patch Observable.prototype; rxjs-no-patched disallows calls to methods via the prototype; and rxjs-no-wholesale disallows the importation of the entire RxJS library.

A custom rule to disallow unused, directly-imported methods is not required; just use TSLint’s no-unused-variable rule.

Rules for the Patch-in-every-file Policy

In shared code bases in which files might be imported individually, a sensible policy is to enforce the importation of patched methods in every file in which they are used.

The rule combination looks like this:

"rxjs-add": { "severity": "error" },
"rxjs-no-unused-add": { "severity": "error" },
"rxjs-no-wholesale": { "severity": "error" }

rxjs-add ensures that any methods used in a module are imported in that module; rxjs-no-unused-add disallows the importation of methods that are not used; and rxjs-no-wholesale disallows the importation of the entire RxJS library.

Rules for the Patch-in-one-file Policy

Another common policy is to nominate a particular file and import patched methods into that file only.

The rule combination looks like this:

"rxjs-add": {
  "options": \[{
    "file": "./source/patched-imports.ts"
  }\],
  "severity": "error"
},
"rxjs-no-wholesale": { "severity": "error" }

rxjs-add ensures that methods that patch Observable.prototype are imported only in the ./source/patched-imports.ts file and disallows the importation of methods that are not used; and rxjs-no-wholesale disallows the importation of the entire RxJS library.

Configuration

To use the custom rules, add rxjs-tslint-rules to the extends setting in your tslint.json file and add the rules use want to use to the rules setting:

{
  "extends": \[
    "rxjs-tslint-rules"
  \],
  "rules": {
    "rxjs-add": { "severity": "error" },
    "rxjs-no-unused-add": { "severity": "error" },
    "rxjs-no-wholesale": { "severity": "error" }
  }
}

rxjs-tslint-rules is not opinionated and contains no enabled, default rules. The rules are explained in more detail in the documentation.

Most of the rules require type checking — which means that TSLint will need to compile the program. More detailed information about configuring TSLint can be found here.


With the release of RxJS version 5.5.0, there is another alternative: importing and using pipeable (also known as lettable) operators.

Pipeable operators have several advantages and if you wish to switch to them, rxjs-tslint-rules can be used enforce a pipeable-operators-only policy. The rule combination for the policy is detailed in my article RxJS: Understanding Lettable Operators.


© 2020 Nicholas Jamieson All Rights Reserved