How to use $destroy to clean up scope watchers?

Loading

What is $destroy?

In AngularJS, the $destroy event is triggered when a scope is about to be removed (such as when navigating away from a page or removing a directive/component).

We use $destroy to clean up scope watchers, event listeners, and timers to improve performance and prevent memory leaks.


1️⃣ Why Use $destroy?

Prevents Memory Leaks – Stops unnecessary event listeners from running after the scope is gone.
Improves Performance – Reduces unnecessary $digest cycles.
Cleans Up Resources – Helps in cleaning up intervals, timeouts, and DOM events.


2️⃣ Cleaning Up $watch Listeners with $destroy

When we use $scope.$watch(), it adds a watcher to the digest cycle. If not cleaned up, it continues running even when the component is removed.

Without $destroy (Risk of Memory Leaks)

app.controller('MainCtrl', function($scope) {
$scope.counter = 0;

// Adding a watch
$scope.$watch('counter', function(newValue, oldValue) {
console.log('Counter changed:', newValue);
});

// When this scope is destroyed, the watcher still runs unless cleaned up
});

Problem: The $watch function stays active even after the controller is destroyed, leading to unnecessary updates.

With $destroy (Proper Cleanup)

app.controller('MainCtrl', function($scope) {
$scope.counter = 0;

// Storing the watcher in a variable
var unwatch = $scope.$watch('counter', function(newValue, oldValue) {
console.log('Counter changed:', newValue);
});

// Cleanup when the scope is destroyed
$scope.$on('$destroy', function() {
unwatch(); // Remove the watcher
console.log('Watcher removed');
});
});

Now, when the controller is destroyed, the $watch function is removed, avoiding unnecessary execution.


3️⃣ Cleaning Up $on Event Listeners with $destroy

If you’re listening for an event using $scope.$on(), it continues even if the scope is destroyed.

Without $destroy

app.controller('MainCtrl', function($scope) {
$scope.$on('myEvent', function(event, data) {
console.log('Event received:', data);
});
});

Problem: The event listener stays active even after the controller is removed, leading to performance issues.

Using $destroy to Remove the Event Listener

app.controller('MainCtrl', function($scope) {
var eventListener = $scope.$on('myEvent', function(event, data) {
console.log('Event received:', data);
});

// Cleanup event listener
$scope.$on('$destroy', function() {
eventListener(); // Remove event listener
console.log('Event listener removed');
});
});

Now, the event listener is removed when the scope is destroyed.


4️⃣ Cleaning Up $timeout and $interval

If you use $timeout or $interval, they keep running even after the component is removed, unless you stop them.

Without $destroy

app.controller('MainCtrl', function($scope, $interval) {
var timer = $interval(function() {
console.log('Interval running...');
}, 1000);
});

Problem: The interval keeps running even if the controller is destroyed.

Using $destroy to Cancel $interval

app.controller('MainCtrl', function($scope, $interval) {
var timer = $interval(function() {
console.log('Interval running...');
}, 1000);

// Cleanup interval on scope destroy
$scope.$on('$destroy', function() {
$interval.cancel(timer);
console.log('Interval cleared');
});
});

Now, the interval is stopped when the controller is destroyed.


5️⃣ $destroy in Directives

If your directive creates watchers or event listeners, you must clean them up when the directive is removed.

Example: Cleanup Inside a Directive

app.directive('customDirective', function() {
return {
restrict: 'E',
scope: {},
controller: function($scope) {
var unwatch = $scope.$watch('someValue', function(newValue) {
console.log('Value changed:', newValue);
});

// Cleanup on directive destroy
$scope.$on('$destroy', function() {
unwatch();
console.log('Directive watcher removed');
});
}
};
});

Now, the directive cleans itself up properly.


6️⃣ $destroy in AngularJS Components

For components, you can clean up watchers in the $onDestroy lifecycle hook.

app.component('myComponent', {
controller: function($scope, $interval) {
var timer = $interval(function() {
console.log('Interval running...');
}, 1000);

this.$onDestroy = function() {
$interval.cancel(timer);
console.log('Component destroyed, interval cleared');
};
}
});

Components use $onDestroy instead of $scope.$on('$destroy').


Summary

What to Clean?How to Clean Up?
$scope.$watch()Call unwatch() inside $destroy
$scope.$on()Call listener() inside $destroy
$interval / $timeoutUse $interval.cancel() or $timeout.cancel() inside $destroy
DOM event listenersUse element.off() inside $destroy
ComponentsUse $onDestroy()

Leave a Reply

Your email address will not be published. Required fields are marked *