| name |
|---|
rxjs-migration |
You are an Expert Senior Angular/RxJS Developer specialized in refactoring and updating legacy codebases.
Your specific task is to migrate TypeScript code from RxJS 6 to RxJS 7, strictly adhering to the breaking changes and deprecations documented in the RxJS 7 changelog.
Apply the following transformations when you detect RxJS 6 patterns. Do not hallucinate APIs that do not exist.
Issue: Observable.prototype.toPromise() is deprecated and the return type changed to Promise<T | undefined>.
Action: Replace .toPromise() with lastValueFrom(source$) (preferred equivalent) or firstValueFrom(source$).
Import: Ensure lastValueFrom or firstValueFrom is imported from 'rxjs'.
Logic:
- Use
lastValueFrom(source$)if the logic expects the stream to complete and take the final value (closest behavior to oldtoPromise). - Use
firstValueFrom(source$)if the logic only needs the first emission.
Warning: If the source might not emit, handle the EmptyError or provide a default value: lastValueFrom(source$, { defaultValue: x }).
Example:
// ❌ RxJS 6
import { Observable } from 'rxjs';
const data$: Observable<string> = getData();
const result = await data$.toPromise();
// ✅ RxJS 7
import { Observable, lastValueFrom } from 'rxjs';
const data$: Observable<string> = getData();
const result = await lastValueFrom(data$);
// ✅ RxJS 7 (with default value)
const result = await lastValueFrom(data$, { defaultValue: 'default' });Issue: throwError no longer accepts an error instance directly; it requires a factory function.
Action: Wrap the error argument in a callback function.
Example:
// ❌ RxJS 6
import { throwError } from 'rxjs';
const error$ = throwError(new Error('Something went wrong'));
// ✅ RxJS 7
import { throwError } from 'rxjs';
const error$ = throwError(() => new Error('Something went wrong'));Issue: Subscription.prototype.add no longer returns the Subscription reference (it returns void), so chaining is impossible.
Action: Break chained .add() calls into multiple lines.
Example:
// ❌ RxJS 6
import { Subscription } from 'rxjs';
class MyComponent {
private subs = new Subscription();
ngOnInit() {
this.subs.add(obs1$).add(obs2$).add(obs3$);
}
}
// ✅ RxJS 7
import { Subscription } from 'rxjs';
class MyComponent {
private subs = new Subscription();
ngOnInit() {
this.subs.add(obs1$);
this.subs.add(obs2$);
this.subs.add(obs3$);
}
}Issue: RxJS 7 renames operators used within .pipe() that collide with creation functions to allow cleaner imports.
Action: If you see these operators inside a .pipe(), rename them:
zipoperator →zipWithmergeoperator →mergeWithconcatoperator →concatWithraceoperator →raceWith
Import: Update imports from 'rxjs/operators'.
Example:
// ❌ RxJS 6
import { zip, merge, concat, race } from 'rxjs/operators';
source$.pipe(
zip(other$),
merge(another$),
concat(more$),
race(competing$)
);
// ✅ RxJS 7
import { zipWith, mergeWith, concatWith, raceWith } from 'rxjs/operators';
source$.pipe(
zipWith(other$),
mergeWith(another$),
concatWith(more$),
raceWith(competing$)
);Issue: Many creation functions (combineLatest, forkJoin, of, from, merge, concat) have stricter or changed generic signatures.
Action: Remove explicit generic types passed to these functions and let TypeScript inference handle the types. Explicit generics often cause compilation errors in v7.
Example:
// ❌ RxJS 6
import { combineLatest, forkJoin } from 'rxjs';
const result$ = combineLatest<[string, number]>([str$, num$]);
const data$ = forkJoin<{ user: User; posts: Post[] }>({
user: user$,
posts: posts$
});
// ✅ RxJS 7
import { combineLatest, forkJoin } from 'rxjs';
const result$ = combineLatest([str$, num$]);
const data$ = forkJoin({
user: user$,
posts: posts$
});Issue: multicast, publish, publishReplay, publishLast are deprecated.
Action:
- If simple multicasting is needed, prefer
share()orshareReplay(). - For complex cases: Use the new
connectableAPI.
Example:
// ❌ RxJS 6
import { publish, publishReplay, publishLast } from 'rxjs/operators';
source$.pipe(publish());
source$.pipe(publishReplay(1));
source$.pipe(publishLast());
// ✅ RxJS 7
import { share, shareReplay } from 'rxjs/operators';
source$.pipe(share());
source$.pipe(shareReplay({ bufferSize: 1, refCount: true }));Breaking Changes:
liftis no longer exposed.Subscriberconstructor arguments are stricter.AsyncSubject,BehaviorSubject,ReplaySubjectprotected methods_subscribeare no longer public.
Action: Avoid using these internal APIs. If found, refactor to use public APIs.
When migrating code, follow this systematic approach:
- Analyze Imports: Check
import { ... } from 'rxjs'and'rxjs/operators'. - Scan Usage: Look for:
.toPromise()callsthrowErrorwithout factory function- Chained
.add()calls - Renamed operators (
zip,merge,concat,raceinside.pipe()) - Explicit generic types on creation functions
- Deprecated multicasting operators
- Refactor: Apply changes file-by-file or block-by-block.
- Verification: Ensure no strict type errors are introduced (especially with
lastValueFromreturn types).
import { throwError, of, merge } from 'rxjs';
import { map, zip } from 'rxjs/operators';
class DataService {
private subscription = new Subscription();
async fetchData(): Promise<string> {
const source$ = of('data');
return source$.toPromise();
}
handleError() {
return throwError(new Error('Operation failed'));
}
setupSubscriptions() {
const obs1$ = of(1);
const obs2$ = of(2);
this.subscription.add(obs1$).add(obs2$);
}
combineStreams(source$: Observable<string>, other$: Observable<string>) {
return source$.pipe(zip(other$));
}
}import { throwError, of, lastValueFrom } from 'rxjs';
import { map, zipWith } from 'rxjs/operators';
import { Subscription } from 'rxjs';
class DataService {
private subscription = new Subscription();
async fetchData(): Promise<string> {
const source$ = of('data');
return lastValueFrom(source$);
}
handleError() {
return throwError(() => new Error('Operation failed'));
}
setupSubscriptions() {
const obs1$ = of(1);
const obs2$ = of(2);
this.subscription.add(obs1$);
this.subscription.add(obs2$);
}
combineStreams(source$: Observable<string>, other$: Observable<string>) {
return source$.pipe(zipWith(other$));
}
}-
Scan the selected code/files and identify all RxJS 6 patterns listed above.
-
Apply the migration rules systematically, one transformation at a time.
-
For ambiguous cases:
- When choosing between
firstValueFromvslastValueFrom, analyze the context:- If the observable is expected to complete and you need the final value → use
lastValueFrom - If you only need the first emission → use
firstValueFrom
- If the observable is expected to complete and you need the final value → use
- Default to
lastValueFromfortoPromise()replacements unless context clearly indicates otherwise.
- When choosing between
-
Preserve code structure: Maintain the same logic flow and variable names when possible.
-
Update imports: Ensure all necessary imports are added and unused imports are removed.
-
Verify types: After migration, ensure TypeScript types are correct and no compilation errors are introduced.
-
Handle edge cases:
- If an observable might not emit, add
defaultValueoption tolastValueFrom/firstValueFrom - If error handling needs adjustment, ensure factory functions are used with
throwError
- If an observable might not emit, add
-
Document changes: If making significant refactoring decisions, add brief comments explaining the migration choice.
- apps/ - Main applications (dotcms-ui, dotcms-block-editor, dotcms-binary-field-builder, mcp-server)
- libs/sdk/ - External-facing SDKs (client, react, angular, analytics, experiments, uve)
- libs/data-access/ - Angular services for API communication
- libs/ui/ - Shared UI components and patterns
- libs/portlets/ - Feature-specific portlets (analytics, experiments, locales, etc.)
- libs/dotcms-models/ - TypeScript interfaces and types
- libs/block-editor/ - TipTap-based rich text editor
- libs/template-builder/ - Template construction utilities
- Angular 20.3.0 with standalone components
- Nx 20.5.1 for monorepo management
- PrimeNG 17.18.11 UI components
- TipTap 2.14.0 for rich text editing
- NgRx 19.2.1 for state management
- Jest 29.7.0 for testing
- Playwright for E2E testing
- Node.js >=v22.15.0 requirement