How to Create and Fix Memory Leaks With Chrome DevTools
By: Rakia Ben Sassi
Tags: JavaScript, Web Development, Google Chrome, Chrome Dev Tools, Programming

We’ve Got a Leak — How Do We Fix It?

Here are a few things that might cause memory leaks in an Angular app:

Missing unsubscription , which will retain the components in the memory

, which will retain the components in the memory Missing unregistration for DOM event Listeners : such as a listener to a scroll event, a listener to forms onChange event, etc.

: such as a listener to a scroll event, a listener to forms event, etc. unclosed WebSocket connections when they are unused

when they are unused Detached DOM tree: A DOM node could be garbage-collected when there are no global references to it. A node is said to be detached when it’s removed from the DOM tree but some JavaScript still references it. Such a case could be identified by comparing two Heap snapshots and then scrolling down to elements prefixed by Detached under the Constructor column.

A visualization of the mark & sweep garbage collection algorithm in action (Source)

When an object is not referenced anymore by something reachable from a global object, it will be reachable by the garbage collector. Objects referencing each other but at the same time not reachable from the root will be garbage collected.

Performance tuning action 1: unsubscribing

Our todo app is already suffering from the first-mentioned two common causes for memory leaks. Each time a need for subscribing to observable arises, it will produce a Subscription object, which, when the component gets destroyed by Angular runtime, should be handled in a way that will not cause memory leaks in the JavaScript runtime — that means calling unsubscribe() , usually inside the ngOnDestroy method of the component.

After maintaining the subscription to formGroup.get('type').valueChanges and removing the subscription to formGroup.valueChanges (since it’s not needed), we added the missing unsubscriptions in todo.dialog.ts and todo-list.component.ts as follows:

Unsubscriptions from observables

Performance tuning action 2: onlySelf & emitEvent

For more fine-grained control over the changes propagation and the DOM events emit, we can use onlySelf: true and emitEvent: false to prevent a form field’s change from triggering the onChange method of its ancestors (the whole form or the list of all forms in the dialog). Before removing the listener to FormGroup valueChanges , you may have noticed that after each change of the value in the Type drop-down, there were two log messages displayed on the browser’s console: one from console.log('form value changed') and the other one from console.log('type changed') because triggering the field event listener was propagated to the parent DOM event listener.

Whenever an unneeded todo form is deleted (with delete icon), you have to remember to unsubscribe from its listeners:

Delete an item form from the formArray

Have we fixed it?

Go and repeat the previous scenario, and then take a performance timeline record for it. See what happens; I’ll wait here. Notice anything interesting? Is there a big difference?

I don’t think so. This improvement was no silver bullet. There’s a bit more slapped onto these memory leaks. So, let’s continue our optimization mission.

Image source: “Performance Tuning: measure, optimize and monitor” by Addy Osmani

Performance tuning action 3: OnPush ChangeDetection

Instead of the default Angular ChangeDetectionStrategy , I used the OnPush strategy in todo dialog:

OnPush ChangeDetectionStrategy in todo.dialog.ts

Performance tuning action 4: Angular pipe & minimize subscriptions

I implemented a custom pure pipe and used it in the selectBox field. The subscription to type’s valueChanges is not needed anymore now.

Please remember that an impure pipe is called often, as often as every keystroke or mouse move, and that an expensive, long-running pipe could destroy the user experience.

selectBox with pipe

Don’t forget to add FilterPerTypePipe to the declarations part in app.module.ts :

I removed the subscription to todoService.getTodos() in the dialog since todoList is already available in the todo list component and can be added to TodoDialogData . I also moved the subscription to todoService.getTypes() to the todo list component. No injection for TodoService is required in TodoDialog :

todo-dialog.ts without any subscription