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 page. The overall process can be carried out in stages:
-
Update to the latest version of RxJS 5.5 and ensure that you've fixed any issues caused by bug fixes.
-
Install RxJS v6 along with the backward-compatibility package,
rxjs-compat
. -
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. -
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]
- Before RxJS release v7, you will need to remove and replace all use of deprecated functionality.
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.
If you have installed rxjs-compat
, there are only two breaking changes that you might need to address immediately.
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.
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 = () => {
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) => {
this.subscribe({
next(value) { subscriber.next(value); },
error(err) { subscriber.error(err); },
complete() { subscriber.complete(); },
});
});
});
source$.pipe(
userDefined(),
)
.subscribe();
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 have changed.
- Operator syntax has changed to use piping instead of chaining.
- Classes that operate on observables have been replaced by functions.
- In functions that have the resultSelector parameter, the parameters have been deprecated in most cases, and removed for two functions. The ones that have been removed must be updated before you can remove the compatibility layer.
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:
- rxjs: Creation methods, types, schedulers and utilities
import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent } from 'rxjs';
- rxjs/operators: All pipeable operators:
import { map, filter, scan } from 'rxjs/operators';
- rxjs/webSocket: The web socket subject implementation
import { webSocket } from 'rxjs/webSocket';
- rxjs/ajax: The Rx ajax implementation
import { ajax } from 'rxjs/ajax';
- rxjs/testing: The testing utilities
import { TestScheduler } from 'rxjs/testing';
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.
- See Operator Pipe Syntax for details of how to refactor using rxjs-tslint.
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()
.
-
ConnectableObservable
is hidden from direct use in v6 and is accessible only through operatorsmulticast
,publish
,publishReplay
, andpublishLast
. -
SubscribeOnObservable
is hidden from direct use in v6 and is accessible only through operatorsubscribeOn
.
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 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.
-
The
resultSelector
parameter forfirst()
andlast()
are removed in v6. If these are used, the code must be updated to run without therxjs-compat
package. -
The
resultSelector
parameter available for many mapping operators has been deprecated for v6 and the implementation re-written to be much smaller. They will continue to work without the compatibility package, but must be replaced before the v7 release. See Deprecations.
See Result Selector Migration for details of which operators are affected and how to move the result-selection functions out of the operator call.
Before RxJS releases v7, you will need to remove and replace all use of deprecated functionality. The following areas contain deprecated functionality:
Observable.if
andObservable.throw
These methods have been replaced by the staticiif()
andthrowError()
functions. Use rxjs-tslint to convert method calls with function calls.
See Convert deprecated methods for details.
-
"Creation" operators The following operators have been moved from
rxjs/operators
torxjs
, and their usage has changed: -
merge
-
concat
-
combineLatest
-
race
-
zip
See Convert deprecated methods for details.
- Result selectors
See Result Selector Migration for details of which operators are affected and how to move the result-selection functions out of the operator call.
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:
do
->tap
catch
->catchError
switch
->switchAll
finally
->finalize
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);
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$);
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.
-
The parameter has been *removed *from the first() and last() operators in v6, but is supported by the rxjs-compat package. You must update your code in order to drop the compatibility package.
-
The parameter is *deprecated *in the following operators, and will be removed in v7. You must update your code before moving to the v7.
- mergeMap()
- mergeMapTo()
- concatMap()
- concatMapTo()
- switchMap
- switchMapTo()
- exhaustMap()
- forkJoin()
- zip()
- combineLatest()
- fromEvent()
first()
- with resultSelector (v5.x)
source.pipe(
first(predicate, resultSelector, defaultValue)
)
- without resultSelector (if you're not using the index in it):
source.pipe(
first(predicate, defaultValue),
map(resultSelector)
)
- without resultSelector (if you ARE using the index in it)
source.pipe(
map((v, i) => [v, i]),
first(([v, i]) => predicate(v, i)),
map(([v, i]) => resultSelector(v, i)),
)
last()
- with resultSelector (v5.x)
source.pipe(
last(predicate, resultSelector, defaultValue)
)
- without resultSelector (if you're not using the index in it):
source.pipe(
last(predicate, defaultValue),
map(resultSelector)
)
- without resultSelector (if you ARE using the index in it)
source.pipe(
map((v, i) => [v, i]),
last(([v, i]) => predicate(v, i)),
map(([v, i]) => resultSelector(v, i)),
)
mergeMap()
- with resultSelector (v5.x)
NOTE: The concurrency-limit argument is optional, shown here for completeness.
source.pipe(
mergeMap(fn1, fn2, concurrency)
)
- the same functionality without resultSelector, achieved with inner map.
source.pipe(
mergeMap((a, i) => fn1(a, i).pipe(
map((b, ii) => fn2(a, b, i, ii))
)),
concurrency
)
mergeMapTo()
- with resultSelector (v5.x)
source.pipe(
mergeMapTo(a$, resultSelector)
)
- without resultSelector
source.pipe(
mergeMap((x, i) => a$.pipe(
map((y, ii) => resultSelector(x, y, i, ii))
)
)
concatMap()
- with resultSelector (v5.x)
source.pipe(
concatMap(fn1, fn2)
)
- the same functionality without resultSelector, achieved with inner map:
source.pipe(
concatMap((a, i) => fn1(a, i).pipe(
map((b, ii) => fn2(a, b, i, ii))
)
)
concatMapTo()
- with resultSelector (v5.x)
source.pipe(
concatMapTo(a$, resultSelector)
)
- without resultSelector
source.pipe(
concatMap((x, i) => a$.pipe(
map((y, ii) => resultSelector(x, y, i, ii))
)
)
switchMap()
- with resultSelector (v5.x)
source.pipe(
switchMap(fn1, fn2)
)
- the same functionality without resultSelector, achieved with inner map
source.pipe(
switchMap((a, i) => fn1(a, i).pipe(
map((b, ii) => fn2(a, b, i, ii))
)
)
switchMapTo()
- with resultSelector (v5.x)
source.pipe(
switchMapTo(a$, resultSelector)
)
- without resultSelector
source.pipe(
switchMap((x, i) => a$.pipe(
map((y, ii) => resultSelector(x, y, i, ii))
)
)
exhaustMap()
- with resultSelector (v5.x)
source.pipe(
exhaustMap(fn1, fn2)
)
- the same functionality without resultSelector, achieved with inner map
source.pipe(
exhaustMap((a, i) => fn1(a, i).pipe(
map((b, ii) => fn2(a, b, i, ii))
)
)
forkJoin()
- with resultSelector (v5.x)
forkJoin(a$, b$, c$, resultSelector)
// or
forkJoin([a$, b$, c$], resultSelector)
- without resultSelector
forkJoin(a$, b$, c$).pipe(
map(x => resultSelector(...x))
)
// or
forkJoin([a$, b$, c$]).pipe(
map(x => resultSelector(...x))
)
zip()
- with resultSelector (v5.x)
zip(a$, b$, c$, resultSelector)
// or
zip([a$, b$, c$], resultSelector)
- without resultSelector
zip(a$, b$, c$).pipe(
map(x => resultSelector(...x))
)
// or
zip([a$, b$, c$]).pipe(
map(x => resultSelector(...x))
)
combineLatest()
- with resultSelector (v5.x)
combineLatest(a$, b$, c$, resultSelector)
// or
combineLatest([a$, b$, c$], resultSelector)
- without resultSelector
combineLatest(a$, b$, c$).pipe(
map(x => resultSelector(...x))
)
// or
combineLatest([a$, b$, c$]).pipe(
map(x => resultSelector(...x))
)
fromEvent()
- with resultSelector (v5.x)
fromEvent(button, 'click', resultSelector)
- without resultSelector
fromEvent(button, 'click').pipe(
map(resultSelector)
)