RxJS v5.x to v6 Update Guide

RxJS v6 has arrived! While this is a major version change (from 5.x to 6.x), we've put in a lot of work to keep the hard breaking changes to a minimum. In most cases, this allows application and library developers to update incrementally and use RxJS v6 without any modifications to their code.

A backward-compatibility layer eases the update process, allowing you to keep your apps working while you address most code changes at your own pace. The overall process can be carried out in stages:

  1. Update to the latest version of RxJS 5.5 and ensure that you've fixed any issues caused by bug fixes.

  2. Install RxJS v6 along with the backward-compatibility package, rxjs-compat.

  3. If your app is affected by the few breaking changes not covered by rxjs-compat, update the affected code according to the instructions provided below.

  4. Eventually, you will want to drop the compatibility layer to complete the update to RxJS v6. Doing so will significantly decrease the size of your apps.

    To refactor TypeScript code so that it doesn't depend on rxjs-compat, you can use rxjs-tslint.

npm i -g rxjs-tslint rxjs-5-to-6-migrate -p [path/to/tsconfig.json]
  1. Before RxJS release v7, you will need to remove and replace all use of deprecated functionality.

Backwards compatibility

In order to minimize the impact of the upgrade, RxJS v6 releases with a sibling package, rxjs-compat, which provides a compatibility layer between the v6 and v5 APIs. Most developers with existing applications should upgrade by installing both rxjs and rxjs-compat at ^6.0.0:

npm install rxjs@6 rxjs-compat@6 --save

For details about this package, see https://www.npmjs.com/package/rxjs-compat.

The compatibility package increases the bundle size of your application, which is why we recommend removing it as soon as your application and dependencies have been updated. This size increase is exacerbated if you are using a version of Webpack before 4.0.0.

For a full explanation of what you will have to update in order to remove rxjs-compat, see Dropping the compatibility layer. Note also that fully updating your application to v6 may expose existing type errors that were not previously shown.

Breaking changes not covered by rxjs-compat

If you have installed rxjs-compat, there are only two breaking changes that you might need to address immediately.

Synchronous error handling

Synchronous error handling (placing a call to the Observable.subscribe() method within a try/catch block) is no longer supported. If it is used, it must be replaced with asynchronous error handling, using the error callback in the Observable.subscribe() method. See examples.

TypeScript prototype operators

If you are defining your own prototype operators in TypeScript and modifying the Observable namespace, you will need to change your operator code in order to get TypeScript to compile. See examples. This is a relatively rare case, likely to affect only advanced TypeScript developers.

Replacing synchronous error handling The following example shows code that subscribes to an observable within a try/catch block, in order to handle errors synchronously:

try { source$.subscribe(nextFn, undefined, completeFn); } catch (err) { handleError(err); }

The following code updates this to handle errors asynchronously by defining an error callback for Observable.subscribe():

source$.subscribe(nextFn, handleError, completeFn);

The next example shows a test that relies on synchronous error handling:

it('should emit an error on subscription', () => { expect(source$.subscribe()).toThrow(Error, 'some message'); });

The following code shows how to correct the test to use asynchronous error handling:

it('should emit an error on subscription', (done) => { source$.subscribe({ error(err) { expect(err.message).toEqual('some message'); } }); });

TypeScript user-defined prototype operators

The following example shows the kind of changes you will need to make in user-defined prototype operators, in order for the TypeScript to compile correctly.

Here is an example of a user-defined prototype operator:

Observable.prototype.userDefined = function () { return new Observable((subscriber) => { this.subscribe({ next(value) { subscriber.next(value); }, error(err) { subscriber.error(err); }, complete() { subscriber.complete(); }, }); }); }; source$.userDefined().subscribe();

To make this code compile correctly in v6, change it as shown here:

const userDefined = <T>() => (source: Observable<T>) => new Observable<T>((subscriber) => { source.subscribe({ next(value) { subscriber.next(value); }, error(err) { subscriber.error(err); }, complete() { subscriber.complete(); }, }); }); }); source$.pipe( userDefined(), ) .subscribe();

Dropping the compatibility layer

If you use functionality that is removed from v6, but supported by the rxjs-compat package, you must refactor or rewrite code to complete the update to v6. The following areas of functionality depend on the compatibility layer:

Import paths

If you're a TypeScript developer, it's recommended that you use rxjs-tslint to refactor your import paths.

For JavaScript developers, the general rule is as follows:

  1. rxjs: Creation methods, types, schedulers and utilities
import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';
  1. rxjs/operators: All pipeable operators:
import { map, filter, scan } from 'rxjs/operators';
  1. rxjs/webSocket: The web socket subject implementation
import { webSocket } from 'rxjs/webSocket';
  1. rxjs/ajax: The Rx ajax implementation
import { ajax } from 'rxjs/ajax';
  1. rxjs/testing: The testing utilities
import { TestScheduler } from 'rxjs/testing';

Operator pipe syntax

The previous coding style of chaining operators has been replaced by piping the result of one operator to another. Pipeable operators were added in version 5.5. For a full discussion of the reasoning and changes required for pipeable operators, see RxJS documentation.

Before you can remove the compatibility layer, you must refactor your code to use only pipeable operators. For Typescript, the tslint tool automates the process to some extent, by applying the transform to well-typed code.

Observable classes

All observable classes (https://github.com/ReactiveX/rxjs/tree/5.5.8/src/observable) have been removed from v6, in favor of existing or new operators that perform the same operations as the class methods. For example, ArrayObservable.create(myArray) can be replaced by from(myArray), or the new operator fromArray().

v6 creation function v5 class
from ArrayLikeObservable
of ArrayObservable
bindCallback BoundCallbackObservable
bindNodeCallback BoundNodeCallbackObservable
defer DeferObservable
empty or EMPTY (constant) EmptyObservable
throwError ErrorObservable
forkJoin ForkJoinObservable
fromEvent FromEventObservable
fromEventPattern FromEventPatternObservable
from FromObservable
generate GenerateObservable
iif IfObservable
interval IntervalObservable
from IteratorObservable
NEVER (constant) NeverObservable
pairs PairsObservable
from PromiseObservable
range RangeObservable
of ScalarObservable
timer TimerObservable
using UsingObservable

Result selectors removed or deprecated

Result selectors are a feature not many people use (in many cases they weren't documented), but were adding significant bloat to the codebase. If you use them, you will need to replace the discontinued resultSelector parameter with external result-selection code.

See Result Selector Migration for details of which operators are affected and how to move the result-selection functions out of the operator call.

Deprecations

Before RxJS releases v7, you will need to remove and replace all use of deprecated functionality. The following areas contain deprecated functionality:

See Result Selector Migration for details of which operators are affected and how to move the result-selection functions out of the operator call.

HowTo: Convert to pipe syntax

Before converting dot-chained operators to pipeable operators, make sure you import all operators used from rxjs/operators. For example:

import { map, filter, catchError, mergeMap } from 'rxjs/operators';

The following operator names were changed because their dot-chained names are reserved words in JavaScript:

To convert dot-chained operators to pipeable operators, wrap all operators in the pipe() method from the source observable, remove the dots, and add commas to pass each operation to pipe() as an argument.

For example, the following code uses chaining:

source .map(x => x + x) .mergeMap(n => of(n + 1, n + 2) .filter(x => x % 1 == 0) .scan((acc, x) => acc + x, 0) ) .catch(err => of('error found')) .subscribe(printResult);

To convert to piping:

source.pipe( map(x => x + x), mergeMap(n => of(n + 1, n + 2).pipe( filter(x => x % 1 == 0), scan((acc, x) => acc + x, 0), )), catchError(err => of('error found')), ).subscribe(printResult);

HowTo: Convert deprecated methods

Observable.if > iif()

Observable.if(test, a$, b$); // becomes iif(test, a$, b$);

Observable.error > throwError()

Observable.throw(new Error()); // becomes throwError(new Error());

merge

import { merge } from 'rxjs/operators'; a$.pipe(merge(b$, c$)); // becomes import { merge } from 'rxjs'; merge(a$, b$, c$);

concat

import { concat } from 'rxjs/operators'; a$.pipe(concat(b$, c$)); // becomes import { concat } from 'rxjs'; concat(a$, b$, c$);

combineLatest

import { combineLatest } from 'rxjs/operators'; a$.pipe(combineLatest(b$, c$)); // becomes import { combineLatest } from 'rxjs'; combineLatest(a$, b$, c$);

race

import { race } from 'rxjs/operators'; a$.pipe(race(b$, c$)); // becomes import { race } from 'rxjs'; race(a$, b$, c$);

zip

import { zip } from 'rxjs/operators'; a$.pipe(zip(b$, c$)); // becomes import { zip } from 'rxjs'; zip(a$, b$, c$);

HowTo: Result selector migration

In RxJS v5.x, a number of operators have an optional resultSelector argument, in which you can pass a function for handling the result of the operations.

If you are using the parameter, you must update your code by moving your result-selection function out of the original operator call, and applying it to the results of the call.

first()

source.pipe( first(predicate, resultSelector, defaultValue) ) source.pipe( first(predicate, defaultValue), map(resultSelector) ) source.pipe( map((v, i) => [v, i]), first(([v, i]) => predicate(v, i)), map(([v, i]) => resultSelector(v, i)), )

last()

source.pipe( last(predicate, resultSelector, defaultValue) ) source.pipe( last(predicate, defaultValue), map(resultSelector) ) source.pipe( map((v, i) => [v, i]), last(([v, i]) => predicate(v, i)), map(([v, i]) => resultSelector(v, i)), )

mergeMap()

source.pipe( mergeMap(fn1, fn2, concurrency) ) source.pipe( mergeMap((a, i) => fn1(a, i).pipe( map((b, ii) => fn2(a, b, i, ii)) )), concurrency )

mergeMapTo()

source.pipe( mergeMapTo(a$, resultSelector) ) source.pipe( mergeMap((x, i) => a$.pipe( map((y, ii) => resultSelector(x, y, i, ii)) ) )

concatMap()

source.pipe( concatMap(fn1, fn2) ) source.pipe( concatMap((a, i) => fn1(a, i).pipe( map((b, ii) => fn2(a, b, i, ii)) ) )

concatMapTo()

source.pipe( concatMapTo(a$, resultSelector) ) source.pipe( concatMap((x, i) => a$.pipe( map((y, ii) => resultSelector(x, y, i, ii)) ) )

switchMap()

source.pipe( switchMap(fn1, fn2) ) source.pipe( switchMap((a, i) => fn1(a, i).pipe( map((b, ii) => fn2(a, b, i, ii)) ) )

switchMapTo()

source.pipe( switchMapTo(a$, resultSelector) ) source.pipe( switchMap((x, i) => a$.pipe( map((y, ii) => resultSelector(x, y, i, ii)) ) )

exhaustMap()

source.pipe( exhaustMap(fn1, fn2) ) source.pipe( exhaustMap((a, i) => fn1(a, i).pipe( map((b, ii) => fn2(a, b, i, ii)) ) )

forkJoin()

forkJoin(a$, b$, c$, resultSelector) // or forkJoin([a$, b$, c$], resultSelector) forkJoin(a$, b$, c$).pipe( map(x => resultSelector(...x)) ) // or forkJoin([a$, b$, c$]).pipe( map(x => resultSelector(...x)) )

zip()

zip(a$, b$, c$, resultSelector) // or zip([a$, b$, c$], resultSelector) zip(a$, b$, c$).pipe( map(x => resultSelector(...x)) ) // or zip([a$, b$, c$]).pipe( map(x => resultSelector(...x)) )

combineLatest()

combineLatest(a$, b$, c$, resultSelector) // or combineLatest([a$, b$, c$], resultSelector) combineLatest(a$, b$, c$).pipe( map(x => resultSelector(...x)) ) // or combineLatest([a$, b$, c$]).pipe( map(x => resultSelector(...x)) )

fromEvent()

fromEvent(button, 'click', resultSelector) fromEvent(button, 'click').pipe( map(resultSelector) )

UMD module name change

In RxJS v6.x, UMD module name has been changed from Rx to rxjs so that it's align with other imports module name.

const rx= Rx; rx.Observable.of(1,2,3).map(x => x + '!!!'); // becomes const { of } = rxjs; const { map } = rxjs.operators; of(1,2,3).pipe(map(x => x + '!!!')); // etc