Memory leaks in AngularJS applications can lead to performance degradation, increased memory consumption, and eventual crashes if not handled properly. This guide explains common causes of memory leaks, how to detect them, and best practices to prevent them.
1. Understanding Memory Leaks in AngularJS
A memory leak occurs when objects remain in memory even though they are no longer needed, leading to gradual memory consumption.
Common Causes of Memory Leaks in AngularJS
🔹 Unremoved $watch
expressions – Keeps watching scope variables even when the component is destroyed
🔹 DOM event listeners not unregistered – Adds unnecessary listeners that don’t get cleaned up
🔹 Detached DOM elements – Keeping references to elements removed from the DOM
🔹 Global variables inside controllers/services – Holding onto memory longer than necessary
🔹 Not destroying $timeout
and $interval
– These can keep executing indefinitely
🔹 Leaking $scope
in long-lived services – Unused scope objects staying in memory
2. Detecting Memory Leaks in AngularJS
🔍 Using Chrome DevTools to Identify Leaks
1️⃣ Open Chrome DevTools (F12
→ Memory
tab).
2️⃣ Take a heap snapshot before interacting with the app.
3️⃣ Perform actions that might cause a memory leak (e.g., navigating between views).
4️⃣ Take another heap snapshot and compare differences.
5️⃣ Look for detached DOM elements or persistent objects that should have been garbage collected.
Checking Active Watchers
Use this command in Chrome DevTools Console:
angular.element(document).injector().get('$rootScope').$$watchersCount;
If this number keeps increasing unnecessarily, you have a memory leak.
3. Preventing Memory Leaks in AngularJS
1. Remove $watch
Expressions When No Longer Needed
Each $watch()
adds a listener to the $digest
cycle. If not removed, it stays in memory even after the component is destroyed.
Bad Practice: Creating a $watch
Without Cleaning Up
$scope.$watch('myVar', function(newValue, oldValue) {
console.log('Value changed:', newValue);
});
Good Practice: Use $destroy
Event to Remove Watchers
var watcher = $scope.$watch('myVar', function(newValue) {
console.log('Value changed:', newValue);
});
// Remove watcher when scope is destroyed
$scope.$on('$destroy', function() {
watcher();
});
2. Remove Event Listeners to Prevent Memory Leaks
Event listeners attached to DOM elements are not automatically cleaned up when elements are removed.
Bad Practice: Adding Listeners Without Removing Them
document.addEventListener('click', function() {
console.log('User clicked!');
});
Good Practice: Remove Event Listeners on $destroy
var handleClick = function() {
console.log('User clicked!');
};
document.addEventListener('click', handleClick);
$scope.$on('$destroy', function() {
document.removeEventListener('click', handleClick);
});
3. Clean Up $timeout
and $interval
$timeout
and $interval
continue executing even after the scope is destroyed unless explicitly stopped.
Bad Practice: Forgetting to Cancel $timeout
$scope.timer = $timeout(function() {
console.log('Task executed');
}, 5000);
Good Practice: Cancel $timeout
on $destroy
var timer = $timeout(function() {
console.log('Task executed');
}, 5000);
$scope.$on('$destroy', function() {
$timeout.cancel(timer);
});
4. Use $scope.$on('$destroy')
to Clean Up Components
AngularJS doesn’t automatically remove listeners, timeouts, or intervals when a component is removed. Always listen for $destroy
to clean up.
$scope.$on('$destroy', function() {
// Cleanup all resources
watcher();
document.removeEventListener('click', handleClick);
$timeout.cancel(timer);
});
Ensures all unnecessary objects are removed from memory.
5. Avoid Using $rootScope
for Persistent Data Storage
The $rootScope
persists throughout the application’s lifecycle. Storing too much data in it can cause unwanted memory retention.
Bad Practice: Storing Large Objects in $rootScope
$rootScope.largeData = { /* Large JSON object */ };
Good Practice: Use Services for State Management
app.service('DataService', function() {
var data = {};
return {
set: function(key, value) {
data[key] = value;
},
get: function(key) {
return data[key];
}
};
});
Keeps data separate from $rootScope
, making it easier to manage.
6. Use One-time Bindings for Static Data
AngularJS re-evaluates all bindings in $scope
, which increases memory usage. Use one-time bindings (::
) for static data.
<p>{{ ::staticData }}</p>
Reduces unnecessary watchers for improved performance.
7. Use Track by in ng-repeat to Optimize DOM Updates
The ng-repeat
directive creates a large number of watchers, slowing down performance.
Bad Practice: Using ng-repeat
Without track by
<div ng-repeat="item in items">
{{ item.name }}
</div>
Recreates DOM elements unnecessarily, causing memory leaks.
Good Practice: Use track by
to Optimize Rendering
<div ng-repeat="item in items track by item.id">
{{ item.name }}
</div>
Prevents unnecessary DOM re-renders, reducing memory consumption.