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.
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done