Skip to main content

Migrating from RxJS v6 to v7

RxJS v7 includes significant improvements to performance, bundle size, and type safety. This guide will help you migrate your application from RxJS v6 to v7.

Prerequisites

TypeScript Requirement: RxJS v7 requires TypeScript 4.2 or higher. Update your TypeScript version before upgrading RxJS.
The rxjs-compat package is not available for v7. You’ll need to address all deprecations before upgrading.

Major Changes Overview

Type System Improvements

RxJS v7 significantly improves TypeScript type inference. Most generic signatures have changed to provide better type inference:
import { combineLatest, of } from 'rxjs';

// Explicit generics required
const result$ = combineLatest<[number, string]>(
  of(1),
  of('hello')
);
The following operators have updated generic signatures. Do not explicitly pass generics - let TypeScript infer them:
  • combineLatest, concat, merge, race, zip
  • forkJoin, partition, bindCallback
  • defer, iif, onErrorResumeNext
  • of, pairs, pipe
  • All combination operators: concatAll, mergeAll, switchAll

Promise Conversion Changes

The toPromise() method is deprecated in favor of two new, more explicit functions:
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';

const result = await interval(1000)
  .pipe(take(10))
  .toPromise(); // Returns T | undefined
Breaking Change: toPromise() now correctly returns Promise<T | undefined> instead of Promise<T>. This reflects the reality that observables can complete without emitting values.
import { EMPTY, firstValueFrom } from 'rxjs';

// Prevent EmptyError by providing a default value
const result = await firstValueFrom(EMPTY, { defaultValue: 0 });
console.log(result); // 0

Subscription Changes

The Subscription.add() method now returns void instead of a Subscription:
import { interval, Subscription } from 'rxjs';

const subscription = new Subscription();
const sub1 = subscription.add(interval(1000).subscribe());
// sub1 could be used for removal

Observable Internal Changes

Several internal implementation details are no longer exposed:
Breaking Change: The lift operator is no longer publicly exposed in TypeScript. Use the documented operator creation patterns instead.
import { Observable } from 'rxjs';

function customOperator() {
  return (source: Observable<any>) => {
    return (source as any).lift(/* ... */);
  };
}

New Features

Animation Frames

A new creation function for working with animation frames:
import { animationFrames } from 'rxjs';
import { map } from 'rxjs/operators';

animationFrames().pipe(
  map(({ elapsed, timestamp }) => ({
    elapsed,
    timestamp
  }))
).subscribe(console.log);

Global Error Handling

Configure global error handlers using the new config object:
import { config } from 'rxjs';

// Handle unhandled errors
config.onUnhandledError = (err) => {
  console.error('Unhandled RxJS error:', err);
};

// Handle notifications after stopped
config.onStoppedNotification = (notification) => {
  console.warn('Notification after stopped:', notification);
};

Enhanced Input Types

RxJS v7 expands ObservableInput to accept more types:
import { from } from 'rxjs';

// AsyncIterable support
async function* asyncGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

from(asyncGenerator()).subscribe(console.log);

// ReadableStream support (from fetch, etc.)
fetch('https://api.example.com/data')
  .then(response => response.body)
  .then(stream => from(stream!))
  .then(obs => obs.subscribe(console.log));

Improved Operators

import { of, timeout } from 'rxjs';
import { delay } from 'rxjs/operators';

// Timeout with absolute date
of('data').pipe(
  delay(5000),
  timeout({
    first: new Date(Date.now() + 1000),
    with: () => of('timeout!')
  })
).subscribe(console.log);

// Timeout between items
of(1, 2, 3).pipe(
  timeout({
    each: 1000,
    with: () => of('too slow!')
  })
).subscribe(console.log);
import { interval, share, ReplaySubject } from 'rxjs';
import { take } from 'rxjs/operators';

// Configure subject type and reset behavior
const shared$ = interval(1000).pipe(
  take(5),
  share({
    connector: () => new ReplaySubject(2),
    resetOnError: true,
    resetOnComplete: true,
    resetOnRefCountZero: true
  })
);

New Combination Operators

To avoid naming conflicts with deprecated operators, new variants were added:
import { of } from 'rxjs';
import { zipWith, mergeWith, concatWith, raceWith } from 'rxjs/operators';

const source$ = of(1, 2, 3);
const other$ = of('a', 'b', 'c');

// New explicit naming
source$.pipe(
  zipWith(other$)
).subscribe(console.log); // [1, 'a'], [2, 'b'], [3, 'c']

Migration Checklist

1

Update TypeScript

Ensure you’re using TypeScript 4.2 or higher:
npm install -D typescript@^4.2.0
2

Remove Generic Type Arguments

Remove explicit generic type arguments from operators like combineLatest, merge, zip, etc. Let TypeScript infer types automatically.
3

Replace toPromise()

Replace all uses of toPromise() with firstValueFrom() or lastValueFrom():
  • Use firstValueFrom() when you want the first emitted value
  • Use lastValueFrom() when you want the final emitted value
4

Update Subscription Handling

If you relied on Subscription.add() returning a subscription, refactor to use the remove() method with the same teardown reference.
5

Address Custom Operators

If you created custom operators using lift, refactor them to use the documented pattern with new Observable().
6

Test Error Handling

Review error handling logic - unhandled errors now throw in their own call stack instead of using console.warn.
7

Update Tests

Error objects now have proper stack properties, which may affect deep equality checks in tests.

Next Steps

For detailed information about specific changes: