Skip to content

Commit b139f86

Browse files
committed
fix(transloco): prevent loading translations when injector is destroyed
This avoids potential memory leaks and ensures correct behavior in reactive streams (e.g. switchMap) by returning EMPTY, which completes immediately. Use-case: - If load() is triggered via an observable (like within switchMap), and the service has already been destroyed (e.g. due to app shutdown, route unload, or SSR teardown), continuing execution would: - Create unnecessary subscriptions and pending async operations (like network calls). - Risk caching invalid/partial translation data. - In SSR, could retain memory or even leak state between requests. Returning EMPTY ensures the observable chain completes cleanly without side effects. Here’s a specific leak scenario this fix prevents: ```js componentLanguage$.pipe( switchMap(lang => translocoService.load(lang)), ).subscribe(); ```
1 parent 345181e commit b139f86

File tree

1 file changed

+10
-0
lines changed

1 file changed

+10
-0
lines changed

libs/transloco/src/lib/transloco.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class TranslocoService {
109109
};
110110

111111
private destroyRef = inject(DestroyRef);
112+
private destroyed = false;
112113

113114
constructor(
114115
@Optional() @Inject(TRANSLOCO_LOADER) private loader: TranslocoLoader,
@@ -144,6 +145,7 @@ export class TranslocoService {
144145
});
145146

146147
this.destroyRef.onDestroy(() => {
148+
this.destroyed = true;
147149
// Complete subjects to release observers if users forget to unsubscribe manually.
148150
// This is important in server-side rendering.
149151
this.lang.complete();
@@ -193,6 +195,14 @@ export class TranslocoService {
193195
}
194196

195197
load(path: string, options: LoadOptions = {}): Observable<Translation> {
198+
// If the application has already been destroyed, return an empty observable.
199+
// We use EMPTY instead of NEVER to ensure the observable completes.
200+
// This is important for operators like switchMap, which rely on the inner observable completing
201+
// before they can subscribe to the next one. NEVER would hang the chain indefinitely.
202+
if (this.destroyed) {
203+
return EMPTY;
204+
}
205+
196206
const cached = this.cache.get(path);
197207
if (cached) {
198208
return cached;

0 commit comments

Comments
 (0)