Skip to content

Commit

Permalink
fix(store-devtools): resolve leak in StoreDevtools
Browse files Browse the repository at this point in the history
This commit updates the implementation of the `StoreDevtools` and adds the
`ngOnDestroy` hook, which ensures the teardown of internal subscriptions. Previously,
this caused an excessive memory leak because subscription callbacks created closures
that captured `this`, preventing `this` from being garbage collected.
  • Loading branch information
arturovt committed Jul 22, 2023
1 parent c96d740 commit b8d85a3
Showing 1 changed file with 15 additions and 7 deletions.
22 changes: 15 additions & 7 deletions modules/store-devtools/src/devtools.ts
@@ -1,4 +1,4 @@
import { Injectable, Inject, ErrorHandler } from '@angular/core';
import { Injectable, Inject, ErrorHandler, OnDestroy } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import {
Action,
Expand Down Expand Up @@ -33,8 +33,8 @@ import { DevtoolsDispatcher } from './devtools-dispatcher';
import { PERFORM_ACTION } from './actions';

@Injectable()
export class StoreDevtools implements Observer<any> {
private stateSubscription: Subscription;
export class StoreDevtools implements Observer<any>, OnDestroy {
private liftedStateSubscription: Subscription;
private extensionStartSubscription: Subscription;
public dispatcher: ActionsSubject;
public liftedState: Observable<LiftedState>;
Expand Down Expand Up @@ -71,7 +71,7 @@ export class StoreDevtools implements Observer<any> {

const liftedStateSubject = new ReplaySubject<LiftedState>(1);

const liftedStateSubscription = liftedAction$
this.liftedStateSubscription = liftedAction$
.pipe(
withLatestFrom(liftedReducer$),
scan<
Expand Down Expand Up @@ -110,7 +110,7 @@ export class StoreDevtools implements Observer<any> {
}
});

const extensionStartSubscription = extension.start$.subscribe(() => {
this.extensionStartSubscription = extension.start$.subscribe(() => {
this.refresh();
});

Expand All @@ -121,13 +121,21 @@ export class StoreDevtools implements Observer<any> {
value: toSignal(state$, { manualCleanup: true, requireSync: true }),
});

this.extensionStartSubscription = extensionStartSubscription;
this.stateSubscription = liftedStateSubscription;
this.dispatcher = dispatcher;
this.liftedState = liftedState$;
this.state = state$;
}

ngOnDestroy(): void {
// Even though the store devtools plugin is recommended to be
// used only in development mode, it can still cause a memory leak
// in microfrontend applications that are being created and destroyed
// multiple times during development. This results in excessive memory
// consumption, as it prevents entire apps from being garbage collected.
this.liftedStateSubscription.unsubscribe();
this.extensionStartSubscription.unsubscribe();
}

dispatch(action: Action) {
this.dispatcher.next(action);
}
Expand Down

0 comments on commit b8d85a3

Please sign in to comment.