How to avoid memory leaks while using Subject

76 Views Asked by At

Here, I'm establishing an observable, and upon a click event, I'm unsubscribing from it. While this action successfully unsubscribes from the observable and clears the interval when using Observable, if I opt for a Subject, the anonymous function generated during the creation of the observable doesn't execute. This leads to a memory leak. How to handle such scenarios?

    let newObs = new Observable<number>(observer=>{
      let i=0;
      let interval = setInterval((f:any)=>{
        i++;
        console.log(i+"in observer");
        observer.next(i)
      },1000)

      return ()=>{
        console.log("will be executed when unsubscribed or on error");
        clearInterval(interval);
      }
    })

    let sub = new Subject<number>();
    newObs.subscribe(sub);
    this.subscription2 = sub.subscribe({
      next: (data: number) => { console.log(data+" subscription2") },
      error: (error: string) => { console.log(error) },
      complete: ()=>{console.log('complete')}
    })

    this.subscription = sub.subscribe(f=>{
      console.log(f+"in subscription");
    },error=>{
      console.log(error)
    },()=>{
      console.log("complete")
    })

to unsubscribe subscriptions on click

ngAfterViewInit(){
    fromEvent(this.mapm.nativeElement,'click').subscribe(res=>{
      console.log(res)
      this.subscription.unsubscribe();
      this.subscription2.unsubscribe();
    },(error:any)=>{
      console.log(error)
    },()=>{
      console.log("completed!")
    })
  }
2

There are 2 best solutions below

2
Bastian Bräu On

Sames goes for your click handler, in case the user does not click the element. In both cases you can use the takeUntilDestroyed() operator:

observable$.pipe(takeUntilDestroyed()).subscribe(...);

This will automatically unsubscribe when the component is destroyed.

In case it's not used in a constructor context, you need to provide a DestroyRef:

private destroyRef = inject(DestroyRef);
...
observable$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(...);

For Angular versions < 16 you can use a custom Subject that emits and complets in ngOnDestroy with takeUntil:

private destroy$ = new Subject<void>();
...
observable$.pipe(takeUntil(this.destroy$)).subscribe(...);
...
ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}
0
Picci On

I suppose you are not unsubscribing the subscription you should unsubscribe.

In other words, to stop the stream, you should do the following

// fist store the subscription to newObs in a variable
const subscription0 = newObs.subscribe(sub);

// then, at the click of the button, you should unsubscribe such subscription
subscription0.unsubscribe()

If you do so, you stop the upstream represented by newObs (i.e. the real source of the stream) and you do not need to unsubscribe the other subscriptions, unless there are other reasons.

Here a simple stackblitz.