Skip to main content

create a memory leak in Angular

Performance is crucial to the success of a web application. As a developer, it’s essential to know how memory leaks are created and how to deal with them.
This knowledge is especially important once your application reaches a certain size. If you aren’t careful about memory leaks, then you may end up in a “memory-leak taskforce”. (Yes, I have also been part of one 😉).
Memory leaks can have multiple sources. However, we believe that in Angular, there’s a pattern to the most common cause of memory leaks. And, there’s also a way to avoid them.

What is memory management

In JavaScript memory is managed automatically. This memory life cycle usually consists of three steps:
  1. Allocate the needed memory
  2. Read and write the allocated memory
  3. Release the memory as soon as it’s not needed anymore.
“This automaticity is a potential source of confusion: it can give developers the false impression that they don’t need to worry about memory management.” (mozilla.org)
If you don’t worry about memory management at all, there’s a chance you might run into a memory leak once your application reaches a certain size.
In essence, memory leaks can be defined as memory that is not required anymore but not released. In other words, some objects are not garbage collected.

How does garbage collection work? 🚛

A garbage collection removes garbage. Its job is to clean up memory that is not needed anymore. To determine which memory is required, the garbage collector uses a “mark and sweep” algorithm. As the name suggests, this algorithm consists of two phases, a mark phase and a sweep phase.

Mark phase

Objects and their references are presented as a tree. The root of this tree is the root node (in JavaScript, the window object). Each object contains a mark flag. In the mark phase, first, the mark bit of all objects is set to false.
at creation, the mark bit of all objects is set to false
Second, the tree of objects is traversed, and all the mark bits of objects which are accessible from the root node via traversal are set to true. All non-reachable objects remain marked=false.
An object is non-reachable if there’s no way to reach it from the root.
reachable objects are marked=true, and unreachable objects are marked=false
All mark bits of non-reachable objects are set to false.
That’s all that happens in the mark phase. No memory has been released yet, but the preliminary work is now in place for the sweep phase.

Sweep phase

Here’s where the memory is released. In this phase, all unreachable objects (objects that are still marked as false) are garbage collected.
object tree after garbage collection — all objects marked as false got garbage collected
This algorithm is performed periodically (each time garbage collection runs). Freeable memory is then managed.
Maybe you are wondering if everything that is marked as false is collected, how can we create a memory leak?
If an object is not needed anymore by our application, but still referenced and accessible from the root node, it will not be garbage collected, since the mark bit of an object is set to true.
The algorithm can not determine if a certain piece of memory is used in our application or not. It’s up to the developer to make this clear.

Memory leaks in Angular

Memory leaks most often arise over time when components are rerendered multiple times, e.g through routing or by using the *ngIf directive. For example, when a power user works a whole day on our application without refreshing the browser.
To mimic this scenario, we created a setup with two components, an AppComponent and a SubComponent.


The AppComponent uses the app-subcomponent in its template. The unique thing about this component is that it uses the setInterval to toggle the hideflag every 50ms. This causes the app-sub component to get rerendered every 50ms, i.e. new instances of the SubComponent class are created. This code mimics the user that works a whole day on the same app without refreshing.
We implemented different scenarios in SubComponents and observed memory changes over time. Note that the AppComponent always stays the same. For each scenario, we will decide if we created a memory leak or not by looking at the memory consumption of the browser process.
If the memory consumption increases over time, we have a memory leak. If it remains more or less constant, there might be no leak or at least not a very obvious one.

Scenario 1: Huge for each loop

The first scenario is a loop that iterates 100'000 times and pushes a random value into an Array. Remember that this component is rerendered every 50ms. Have a look at the code and try to find out whether we created a memory leak or not.
Scenario 1: Subcomponent that iterates 100'000 times inside the constructor
Well, even though you should not write such code in production, this code is not causing a memory leak, the memory remains within a constant range of 15MB. So, no leak. Don’t worry; we will explain later why 😉

Scenario 2: Subscribe to a BehaviourSubject

In this scenario, we subscribe to a BehaviourSubject and assign the value to a const. Does this code contain a memory leak? Again, remember the component is rerendered every 50ms.
Scenario 2: subscribe to a subject inside the constructor and assign the value to a local const
The answer is still the same. No memory leak here.

Scenario 3: Assign values inside subscribe to a field

Same code as before, the only difference that we assign the value to a field. And now, what do you think, still no memory leak?
Scenario 3: Subscribe to a Subject and assign the value to a class field
Yes, you are right, again, no memory leak here.
For example 1 we had no subscription. In scenarios 2 and 3, we subscribed to a stream of an observable that was initialized in our component. It seems like we are safe in scenarios where we subscribe to component streams.
But what if we add a DummyService.

Scenarios with a service

In the following scenarios, we are going to do revisit the scenarios above, but this time we will subscribe to a stream exposed by a DummyService.


The DummyService is simple. Just a typical service that exposes a stream(some$) in the form of a public class field.

Scenario 4: Subscribe to exposed stream and assign local const

Let’s use the same situations from above, but this time we subscribe to the some$of the DummyService instead of a component field.
Do we have a memory leak here? Again remember that this component is used inside our AppComponent and rendered multiple times.
Scenario 4: Subscribe to the some$ exposed by the DummyService and assign it to a local const
Well, at this point, we finally created a memory leak, but only a small one.😉 With a “small one,” we mean that the memory does increase slowly over time (barely noticeable, but a glance at the heap snapshot will reveal many Subscriber instances that are not removed).

Scenario 5: Subscribe to dummy service and assign to field member

Again, we subscribe to the dummyServbice. This time though, we assign the received value to a class field instead of a local const.
Scenario 5: Subscribe to the stream of the DummyService and assign the value to a class field
At this point, we finally created a significant memory leak. The memory consumption quickly increases above 1GB after a minute. Let’s see why.

When do we create a memory leak

Maybe you noticed that we didn’t create a memory leak in the first three scenarios. Well, the first three scenarios have something in common; all the references are local to the component.
When subscribing to an observable, the observable keeps a list of its subscribers, in this list, there is our callback, and the callback might reference our component.
When our component is destroyed, i.e. not referenced anymore by angular and thus not reachable from the root node, the observable and its list of subscribers is not reachable from the root node anymore, and the whole component object is garbage collected.
As long as we subscribe to observables that are only referenced within the component, we do not have an issue. It changes, however, once a service comes into play.
As soon as we subscribe to an Observable exposed by a service or a different class, we create a memory leak. This happens because the observable, its list of subscribers, our callback and hence our component are still accessible from the root node, although our component is not referenced by Angular directly. Therefore the component is not garbage collected.
To be clear, you can still use this approach, but you need to handle it the right way!

Handle the subscription

To avoid memory leaks, it’s essential to unsubscribe from an Observable correctly when the subscription is not needed anymore, e.g. when our component is destroyed. There are different ways to unsubscribe Observables.
In our experience, from consulting large enterprise projects, we think its best to use a destroy$ Subject in combination with the takeUntil operator.


We implement the ngOnDestroy lifecycle hook on our component. Every time the component gets destroyed we call nextand complete on our destroy$.
Calling complete is important because it cleans up the subscription from our destroy$.
We then use the takeUntil operator and pass our destroy$ stream to it. This guarantees that the subscription is cleaned (unsubscribed) once our component gets destroyed.

How do I remember to unsubscribe

It’s easy to forget to add a destroy$ to your component and call next and complete in the ngOnDestroy lifecycle hook. Even though I taught project teams to do so, I forgot it many times in components myself.
Fortunately, there’s an awesome lint-rule written by Esteban Gehring which ensures that we correctly unsubscribe. You can simply install it with the following command
npm install @angular-extensions/lint-rules --save-dev
and add it to your tslint.json
{
  "extends": [
    "tslint:recommended",
    "@angular-extensions/lint-rules"
  ]
}
I highly recommend you to use this lint rule in your project. It can save you from hours of debugging sources of unwanted memory leaks.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...