When “leak” words come to mind it means something is happening messy. Sometimes it becomes really hard to find where the issue is and hard to resolve it. In software development, it is one of the basic things to write a clean code that can make you different than others. Sometimes when we develop the larger applications as time goes it grows with code and it becomes slow. It is really essential to know from the beginning to keep our application more cleaner to reduce the bundle sizes and avoid any leaks.
As discussed Memory leak happens in each and every application, Let’s discuss how impact the Angular Application. In this article, we will create a memory leak in the Angular Application and will resolve and discuss different methods to resolve it.
When an application becomes slower we mostly refresh or reload the page so it releases memory and the application becomes normal but we don’t know what is actually happening in the background until we dig more about it.
There are some reasons which sometimes produce memory leaks in Angular.
- working on the same application for a long time.
- Using ngIf to render the application multiple times which depends on the observables.
- calling multiple observables in the applications and not unsubscribing it after use.
- subscribing to the listeners such as form controls and not unsubscribing it after the component gets destroyed.
- when we use shareReplay but not add the unsubscribe logic to handle it.
Let’s create some of the scenarios to discuss the memory leak.
1. Observables subscribing/unsubscribing
Let’s check out the issue and solutions related to Observables then.
Let’s create TestLeakChild and TestLeakParentComponent.
TestLeakChild has a timer to run every second with the console of timer started with the Date and Time.
The ParentLeakComponent has two buttons of start observable and stops observable which will run timer and stop the timer.
Let’s check the demo.
- I clicked on the start observable button
- I clicked the start observable button and the console is printing the timer value
- I clicked on the stop observable button and still console is printing. Isn’t it suppose to stop once clicked on the stop observable button?
- Yes, you were guessing right we have successfully created the memory leak.
- How we can monitor though?
Let’s check this out.
First, we need to open the performance monitor in the browser by following the method.
Let’s follow the same steps and check the Chrome Performance monitor by clicking the start observable and stop observable buttons continuously.
If you noticed in the GIF (sorry it’s a bit blurry) we can see CPU usage and heap size started increasing.
So what should we do to resolve this issue?
The common solution is to go with the unsubscribe method to unsubscribe the Observable once it’s used.
Let’s check the solution by creating TestFixChildComponent and TestFixParentComponent.
Let’s check the behavior once we use the unsubscribe method then.
There are multiple ways to clean up RXJS subscription. We will talk about them later in this article.
You can play around with stackblitz here.
To start with let’s understand what is ShareReplay. It returns the hot observable and replays the N number of notifications, so once the source completes it replayed to the subscribers without calling the source again.
When we see the above screenshot we can see by changing the tabs can make the heap size increasing and the counter is not stopping by itself.
There was one of the things we missed here.
ShareReplay’s source code
We missed property refCount: true, the subscription will never be unsubscribed.
Please check the below article to know more about it.
In order to fix this, Let’s add this:
As we can see now our memory leak is gone.
3. Event Listeners
Another common cause of memory leak is a DOM event that is not unregistered so it will always listen to some events.
Let’s check one common example where we register one event on the body and never unregister it.
This will create a memory leak when we instantiate ScrollComponent.
Once we destroy the component basically we need to unregister the listeners to avoid memory leaks.
We have discussed a lot about Memory Leaks, Let’s check some of the common coding practices to avoid memory leak in our Angular Application.
1. Use the unsubscribe method
All subscriptions need to unsubscribe to release or cancel the Observable executions.
To prevent memory leaks one of the common practices is to capture the Observable in the subscription and unsubscribe it in the ngOnDestroy hook which is basically gets called once the component gets destroyed.
To prevent these memory leaks we have to unsubscribe from the subscriptions when we are done with them. We do so by calling the unsubscribe the method in the Observable.
As we can see we added ngOnDestroy to our TestComponent and we are capturing the Observable in the subscription method and we are unsubscribing it.
What if we do have multiple subscriptions in the component?Let’s check how it looks like.
There are two subscriptions in TestComponent. They are both unsubscribed in the ngOnDestroy hook preventing memory leaks.
We can gather them subscriptions in an array and unsubscribe from them in the ngOnDestroy:
Observables subscribe method returns an object of RXJS subscription so basically, we can group them using the add method so when we unsubscribe one main Subscription all children will be unsubscribed by itself.
Let’s check out the example for this:
This will unsubscribe this.subscripton1$, and this.subscripton2$ when the component is destroyed.
2. Use Async | Pipe
Async pipe subscribes to an Observable and returns the latest emitted value. Async pipe mark component checked by itself once the new values capture. Async pipe automatically unsubscribes to avoid any potential memory leaks.
Let’s check the example of the Async pipe:
So the major advantage of Async Pipe is we don't need to remember to unsubscribe our Observable in ngOnDestroy method as it will take care of by itself.
3. Use RxJS take* operators
RXJS does have some take operators which can be used to unsubscribe Observables.
This operator makes sure that subscriptions happen once or based on the number n times based on the number mentioned.
Mostly it is used with take(1) to make sure that we receive observable value only once and it completes after that.
This operator will be effective when we want a source Observable to emit once and then unsubscribe from the stream directly.
In the above example, data$ will unsubscribe after it gets the first value of data from the service.
The only issue is that even our component is destroyed data$ won't unsubscribe until it gets value.
So it is better to make sure everything is canceled in the ngOnDestroy hook:
This operator emits values emitted by the source Observable until a notifier Observable emits a value.
One of the common approaches used for unsubscribing the Observable is to use takeUntil operator which will wait until we get the data from the service. Once we get data it will unsubscribe the data$.
When we have multiple subscriptions this approach helps to declare only one time and only one time we need to write code in the ngOnDestroy hook to unsubscribe it.
4. Use RxJS first operator
This operator is like the combination of take(1) and takeWhile
If it is called without value then it will complete upon the first value. If it is called with a predicate function, it emits the first value of the source Observable that pass the test condition of the predicate function and complete.
The data$ will complete if the interval emits its first value.
5. Use Decorator to automate Unsubscription
There is a lot of chance that we might forget to write code or there might be any reason we ended up cleaning subscriptions.
We can create our own decorator which can help to avoid writing the code again and again.
Here is one of the implementations of the npm package name unsubscribe all.
By doing so we just need to write UnSubsribeAll() decorator in every class and other things will be taken care of by the decorator.
6. Use tslint
Some might need a reminder by tslint, to remind us in the console that our components or directives should have a ngOnDestroy method when it detects none.
We can add a custom rule to tslint to warn us in the console during linting and building if it finds no ngOnDestroy hook in our components:
You can follow this article to implement the lint rules.
If we have a component like this without ngOnDestroy:
Linting AppComponent will warn us about the missing ngOnDestroy hook:
$ ng lint
Error at app.component.ts 12: Class name must have the ngOnDestroy hook
We can use third-party libraries to achieve these functions.
Example using the sink property to collect the subscriptions using a setter.
- Understand and prevent the most common memory leaks in Angular application - Subscription unsubscribe
- Angular & RxJS: Detecting Memory Leaks
- 6 Ways to Unsubscribe from Observables in Angular
- Angular/RxJs When should I unsubscribe from `Subscription`
Are you preparing for interviews? Here are frequently asked interview questions in Angular. It covers the Latest interview questions for Angular and Frontend development. Let’s check how many of these questions you can answer?
- Daily Angular Development Tips
- Angular Interview Questions You Should Know
- TypeScript Tips and Tricks