Angular unsubscribe, Angular async pipe, RxJS subscribe - Avoid Memory Leaks
Angular unsubscribe, Angular async pipe, RxJS subscribe - Avoid Memory Leaks

In this post you will learn all possible ways to unsubscribe from streams inside Angular.

The first question is do we have a problem at all? Inside Angular we create components. Typically you want to use RxJS streams inside your components. And you write something like this.

data$ = interval(1000)

ngOnInit(): void {
  this.data$.subscribe(data => {
    console.log(data)
  })
}

or maybe you work with HTTP client to get data.

It is all working but actually you have here a memory leak.

Why it happens? Angular doesn't clean subscribers inside your components when component is destroyed. So you just to another page, your component is destroyed but all subscriptions are still hanging there. It will be never deleted.

This is why you must work with your subscribes by yourself because Angular doesn't do anything about them.

Let's look on code that we wrote before. We just have a stream with interval and a subscribe.

Interval

We have this stream on the Posts page but when we just to Homepage it is still there. Which actually means that we must remove all our subscriptions at the moment when we destroy a component.

Unsubscribe

The most obvious way is to unsubscribe from this subscription.

data$ = interval(1000)
dataSubscription: Subscription|undefined

ngOnInit(): void {
  this.dataSubscription = this.data$.subscribe(data => {
    console.log(data)
  })
}

ngOnDestroy(): void {
  this.dataSubscription?.unsubscribe()
}

We defined dataSubscription property and assigned our subscription there. In ngOnDestroy we called unsubscribe to remove it. Most importantly it is undefined by default to we must put a question mark for check.

As you can see in browser after jumping to Homepage we don't have running stream anymore.

Async pipe

Another popular solution which is even better than the first one is just to use async pipe. It fits our needs really well as inside Angular we typically want to render data.

We must use subscribe only in places where we want to call a function. If you don't need to call a function then use pipe

<div>{{ data$ | async }}</div>

With this code we don't have any subscriptions, we don't use subscribe and this async pipe will be subscribed and unsubscribed by Angular itself. This is the best possible variant to use streams inside Angular.

Take (n)

But it is not always possible to write async pipe. Sometimes you really need to call some function. When we need to use subscribe we can use additional functions which will help us avoid unsubscribes.

this.data$.pipe(take(1)).subscribe((data) => {
  console.log('data', data);
});

Here we used take function before writing subscribe. If we set it to 1 it means that we will emit value inside our stream only once. This code doesn't need unsubscribe at it will be never triggered again.

Take while

In a same way we can write some predicate to disallow stream updates.

this.data$.pipe(takeWhile(value => value < 5)).subscribe((data) => {
  console.log('data', data);
});

In this case we used takeWhile function where we provided a predicate. Here we only emit stream if our value is less than 5.

Take until

But the most popular way to prevent streams on destroy is takeUntil function.

unsubscribe$ = new Subject<void>();

ngOnInit(): void {
  this.data$.pipe(takeUntil(this.unsubscribe$)).subscribe((data) => {
    console.log('data', data);
  });
}

ngOnDestroy(): void {
  this.unsubscribe$.next();
  this.unsubscribe$.complete();
}

Here we defined a Subject. It allows us to change it with next and finish with complete. For our stream we use takeUntil function where inside we provide our Subject. Inside ngOnDestroy we trigger change and complete our subject. In this case all streams will takeUntil stop subscribes because our this.unsubscribe$ stream is completed.

It is the best possible variant because for any stream we simply add takeUntil and it will be successfully destroyed.

Custom take until

But you for sure what to say that writing Subject in every component is verbose so we can make this code sharable.

// unsub.class.ts
@Injectable()
export abstract class Unsub implements OnDestroy {
  protected unsubscribe$ = new Subject<void>();

  ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
export class PostsComponent extends Unsub implements OnInit {
  data$ = interval(1000);

  ngOnInit(): void {
    this.data$.pipe(takeUntil(this.unsubscribe$)).subscribe((data) => {
      console.log('data', data);
    });
  }
}

Here we created an abstract class which implements Subject und ngOnDestroy. Then in our component we can simple extend this class and use this.unsubscribe$ without any problems.

And actually if you are interested to learn RxJS functions that I use inside Angular every single day make sure to check this post also.

📚 Source code of what we've done