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:
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]
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
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 = 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();
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:
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:
import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';
import { map, filter, scan } from 'rxjs/operators';
import { webSocket } from 'rxjs/webSocket';
import { ajax } from 'rxjs/ajax';
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.
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 operators multicast
, publish
, publishReplay
, and publishLast
.
SubscribeOnObservable
is hidden from direct use in v6 and is accessible only through operator subscribeOn
.
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 for first()
and last()
are removed in v6. If these are used, the code must be updated to run without the rxjs-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
and Observable.throw
These methods have been replaced by the static iif()
and throwError()
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
to rxjs
, 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.
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) )
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
© 2015–2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors.
Code licensed under an Apache-2.0 License. Documentation licensed under CC BY 4.0.
https://rxjs.dev/guide/v6/migration