In AngularJS, the $destroy
event is supposed to be triggered when a scope is removed or a controller is destroyed. However, there are cases where $destroy
is not triggered, leading to memory leaks and unexpected behaviors.
Step 1: Understanding When $destroy
Should Trigger
What Normally Triggers $destroy
?
- When a Controller is Destroyed
- Happens when you navigate away from a view in an ngRoute or UI-Router app.
- When a Directive’s Element is Removed
- Happens when a directive is destroyed due to a UI change (
ng-if
,ng-repeat
, etc.).
- Happens when a directive is destroyed due to a UI change (
- When a Scope is Manually Destroyed
- Happens when
$scope.$destroy()
is called explicitly.
- Happens when
Common Issue: $destroy
Not Triggering
If $destroy
is not triggered, it means:
- The scope is not actually removed.
- The component is still in the DOM.
- The controller or directive was not properly cleaned up.
Step 2: Diagnosing Why $destroy
is Not Triggering
1. Are You Using ng-if
Instead of ng-show
?
ng-if
removes the element from the DOM → Triggers$destroy
.ng-show
only hides the element → Does NOT trigger$destroy
.
Example (Does NOT trigger $destroy
)
<div ng-show="isVisible">
<my-directive></my-directive>
</div>
- Fix: Use
ng-if
instead.
<div ng-if="isVisible">
<my-directive></my-directive>
</div>
Now, when isVisible
becomes false
, the element is removed, and $destroy
will trigger.
2. Are You Using ng-repeat
Without a Unique Key?
If you use ng-repeat
without a unique track by key, Angular may reuse the scope instead of destroying it.
Example (Does NOT trigger $destroy
)
<div ng-repeat="item in list">
<my-directive></my-directive>
</div>
- Fix: Add
track by item.id
<div ng-repeat="item in list track by item.id">
<my-directive></my-directive>
</div>
Now, when list
changes, old items are removed properly, triggering $destroy
.
3. Are You Manually Removing Elements Without Telling Angular?
If you remove elements using document.getElementById()
or jQuery
, Angular doesn’t know the scope should be destroyed.
Example (Wrong)
document.getElementById('element').remove();
- Fix: Use
$destroy()
before removing manually.
var scope = angular.element(document.getElementById('element')).scope();
scope.$destroy();
document.getElementById('element').remove();
4. Are You Using $timeout
or $interval
Without Cleanup?
If you start a $timeout
or $interval
, but the scope is destroyed before it completes, it may prevent $destroy
from triggering.
Example (Potential Issue)
$timeout(function() {
console.log('Executing after delay');
}, 5000);
- Fix: Cancel
$timeout
or$interval
inside$destroy
var timeoutPromise = $timeout(function() {
console.log('Executing after delay');
}, 5000);
$scope.$on('$destroy', function() {
$timeout.cancel(timeoutPromise);
});
5. Is Your Directive Using scope: false
?
If your directive uses scope: false
, it shares the parent scope. This means $destroy
won’t trigger on the directive.
Example (Does NOT trigger $destroy
)
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: false, // Shares parent scope
link: function(scope) {
scope.$on('$destroy', function() {
console.log('Directive destroyed');
});
}
};
});
- Fix: Use
scope: true
scope: true, // Creates an isolated scope
Step 3: Debugging $destroy
Issues
1. Log When $destroy
Is Triggered
$scope.$on('$destroy', function() {
console.log('$destroy triggered for', $scope);
});
If this doesn’t log anything, $destroy
is not triggering.
2. Check If the Element is Actually Removed
console.log(document.getElementById('my-element'));
If it still exists in the DOM, $destroy
won’t trigger.
3. Check If Any Pending Watches Are Keeping the Scope Alive
console.log($scope.$$watchers);
If there are many watchers, something is keeping the scope active.
Step 4: Ensuring $destroy
Triggers Properly
Use ng-if
Instead of ng-show
<div ng-if="isVisible">
<my-directive></my-directive>
</div>
Ensure ng-repeat
Uses track by
<div ng-repeat="item in list track by item.id">
<my-directive></my-directive>
</div>
Manually Remove Scope Before Deleting an Element
var scope = angular.element(document.getElementById('element')).scope();
scope.$destroy();
document.getElementById('element').remove();
Cancel $timeout
or $interval
Inside $destroy
var timeoutPromise = $timeout(function() {
console.log('Executing after delay');
}, 5000);
$scope.$on('$destroy', function() {
$timeout.cancel(timeoutPromise);
});
Final Thoughts
If $destroy
is not triggered, it usually means:
- The element is not actually removed.
- Angular is reusing the scope.
- A watcher or service is keeping the scope alive.
Fixing this ensures better memory management and prevents performance issues in your AngularJS app.