Photo by Monika Stawowy on Unsplash

Introducing unintentional dependencies on rxjs-compat is something that I see developers doing every now and then. Let’s have a look at rxjs-compat to see what it is, how it works and how depending upon it can be avoided.

So what is it?

In RxJS version 6, breaking changes made the library simpler:

  • the prototype-patching operators were removed; and
  • the export locations were rearranged so that each export was available from only a single location.

Those changes made the library easier to maintain, document and explain, but they created a burden for developers with large RxJS-version-5 codebases: pretty much all RxJS-related code would need to be modified.

The rxjs-compat package was created so that developers could upgrade to RxJS version 6 without having to modify code. It re-implements the prototype-patching operators and makes available all of the RxJS-version-5 export locations.

How does it work?

The RxJS-version-6 distribution includes files for all of the version-5 export locations. However, the imports within those files redirect to rxjs-compat.

For example, let’s look at the of observable creator and the mapTo operator.

In RxJS version 5, you can choose to import everything:

import Rx from "rxjs/Rx";
const answer = Rx.Observable.of(6 * 9).mapTo(42);

Or you can choose to patch Observable with only the creators and operators you need:

import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
import "rxjs/add/operator/mapTo";
const answer = Observable.of(6 * 9).mapTo(42);

Or you can choose to directly import only the creators and operators you need:

import { of } from "rxjs/observable/of";
import { mapTo } from "rxjs/operator/mapTo";
const answer = mapTo.call(of(6 * 9), 42);

With rxjs-compat installed, all of the above snippets will work with RxJS version 6 — despite their using version-5 export locations.

So how does this work? Well, the RxJS-version-6 package includes all of the files imported in the snippets:

node_modules/rxjs/Rx.js
node_modules/rxjs/Observable.js
node_modules/rxjs/add/observable/of.js
node_modules/rxjs/add/operator/mapTo.js
node_modules/rxjs/observable/of.js
node_modules/rxjs/operator/mapTo.js

But each of those files contains nothing apart from the import of an rxjs-compat file. For example, rxjs/Observable.js looks like this:

"use strict";
function __export(m) {
(varp in m)
if (!exportshasOwnProperty(p)) exports[p] = m[p];
}
ObjectdefineProperty(exports, "__esModule", { value: true });
__export(require("rxjs-compat/observable/of"));
//

And rxjs-compat/observable/of.js looks like this:

"use strict";
ObjectdefineProperty(exports, "__esModule", { value: true });
varrxjs_1=("rxjs");
exportsof = rxjs_1of;
//

So importing of from a version-5 export location, imports of from rxjs-compat which imports everything from rxjs.

How can it be avoided? — TL;DR

If an RxJS-version-5 export location us used — without having rxjs-compat installed — an error something like this is effected:

ERROR in ~/project/node_modules/rxjs/observable/of.js
Module not found: Error: Can't resolve 'rxjs-compat/observable/of'
in '~/project/node_modules/rxjs/observable'

Which is a little confusing — unless you know how rxjs-compat works — so it’s understandable that developers sometimes resort to installing rxjs-compat to resolve the error. It’s likely to continue to cause confusion, too, as there are so many snippets — on Stack Overflow and elsewhere — that use version-5 export locations.

The preferred way to resolve the error is to import only from RxJS-version-6 export locations. Fortunately, there are only a few:

import { /* classes, creators, schedulers */ } from "rxjs";
import { ajax } from "rxjs/ajax";
import { /* pipeable operators */ } from "rxjs/operators";
import { TestScheduler } from "rxjs/testing";
import { webSocket } from "rxjs/webSocket";

So, in RxJS version 6, the snippets above should be rewritten as:

import { of } from "rxjs";
import { mapTo } from "rxjs/operators";
const answer = of(6 * 9).pipe(mapTo(42));

To help avoid the problem, I’ve added a TSLint rule — rxjs-no-compat — to rxjs-tslint-rules that bans imports from RxJS-version-5 export locations.

RxJS core team member; front-end developer; mentor; speaker; open-source contributor

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store