Handling Memory Leak in Angular

In this article, we will look through an angular project being slower over time, and try to fix the issue in that project. Let's get started with what is Angular, which we already know. :)

Angular is a web framework to build Single Page web applications. Angular is a TypeScript based open source project. AngularJS was originally developed in 2009 by Miško Hevery at Brat Tech LLC, Angular is a complete rewrite of AngularJS. Angular was announced in October 2014, The first final version was released on September 2016.

Here we will talk about avoiding a memory leak on your Angular application. Memory Leaks does not affect much when your application is small and have very few transactions. I have learned the hard way, and here I am hoping it will help.

My Angular Memory Leak Story

If you had read my previous article Angular Material Data Table Paginator with Server Side Pagination. which discussed implementing server-side pagination on Material Data Table. I have started working on an existing angular project. Thus I build some basic understanding on Angular to understand the existing code and make changes as required. The project is an internal dashboard tracking the activities and providing assistance to the users. Then we started getting 5-6 times more traffics then usual the web app starts lagging, Thus we have to go through to fix the issue.



Memory Leak

Memory Leak is the condition where the memory that is used by the application while performing certain tasks is not released free to be used for other uses.  A memory leak reduces the performance by reducing the amount of available memory. Eventually, in the worst case, too much of the available memory may become allocated and all or part of the system or device stops working correctly, the application fails.

Garbage Collection

We don't need to worry much about memory on javaScript, it is handled automatically. In Angular, nodes are created on tree structure and can be travelled from the root node. So anything that is accessible from the root node is not cleared while those nodes which cannot be accessed from the root server are cleared.

I have a demo project to demonstrate the memory leak. Here we have a service called "SocketServiceService", 

1 import { Injectable } from '@angular/core';
2 import { Observable } from 'rxjs';
3
4 import * as Socket from 'socket.io-client';
5
6 @Injectable({
7 providedIn: 'root'
8 })
9 export class SocketServiceService {
10
11 private socket;
12 private url = "http://localhost:5050";
13
14 constructor() {
15 this.socket = Socket.io(this.url);
16 }
17
18 getNewUser = () =>{
19 //log
20 let aObservable = new Observable(((observable) =>{
21 this.socket.on("newUser", (data:any)=>{
22 observable.next(data);
23 })
24 }))
25 return aObservable;
26 }
27
28 getNewRequest = () =>{
29 let aObservable = new Observable(((observable) =>{
30 this.socket.on("newRequest", (data:any)=>{
31 observable.next(data);
32 })
33 }))
34 return aObservable;
35 }
36 }


Copied the code from VS code, wow it looks cool.

As you see getNewUser() and getNewRequest() will return an Observable which we can subscribe. In the components, we subscribe to these Observables.

For the Dashboard Application, we run the web app the entire day, there are multiple pages, which we travel back and forth. As we all know when we move away from a component, it will be destroyed automatically by Angular. But Wait.

As we had already discussed, anything that is accessible from the root will not be garbage collected. In the case the service keeps track of the subscribed thus, it cannot be clear, but every time we navigate back to the component using this service new component will be created and it will subscribe, the cycle goes for the entire day, thus we start to face the lagging.

Also for every method call, we are creating new Observable and returning it, which need to be improved. Here is the repo Angular-Memory-Leak - Memory Leak Branch.

Unsubscribe the Observable

Let's fix the issue, What if we can clear the reference to the service while the component is being destroyed? Ya, it will be perfect, we can do so in angular onDestroy hook.


Here is Component subscribing for the newUser event.

1 import { Component, OnInit } from '@angular/core';
2 import { SocketServiceService } from 'src/app/service/socket-service.service';
3
4 @Component({
5 selector: 'app-new-user',
6 templateUrl: './new-user.component.html',
7 styleUrls: ['./new-user.component.css']
8 })
9 export class NewUserComponent implements OnInit {
10
11 users: any = [];
12
13 subscriptions = [];
14
15 constructor(
16 private socketService: SocketServiceService
17 ) { }
18
19 ngOnInit(): void {
20 let aSubScription = this.socketService.getNewUser().subscribe((data:any)=>{
21 console.log(data);
22 this.users.unshift(data)
23 })
24 this.subscriptions.push(aSubScription);
25 }
26
27 ngOnDestroy(): void{
28 // console.log("On Destroy called");
29 this.subscriptions.forEach(element => {
30 element.unsubscribe();
31 });
32
33 }
34 }


Here we capture the subscription and when the ngOnDestory() method is called at the end of the component lifecycle, we all unsubscribe from the observable, making the component totally cleared from the root, thus can be garbage collected.

Another possible solution is to instead of having a socket server on service, we can create a connection from the server and listen to the event from the component itself, which will be immediately cleared when the component is destroyed. Check the implemented solution Angular-Memory-Leak-main branch.

Conclusion

Garbage collector collects unused resources and cleared it from the memory if any component remains referenced, it cannot be cleared. Since Service are always attached to the root node and any unsubscribed observables will keep the component as active and thus cannot be cleared, we can either unsubscribe from the observable using the ngOnDestory hook or listen to the event within the component.

0 Comments