![]()
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-ifremoves the element from the DOM → Triggers$destroy.ng-showonly hides the element → Does NOT trigger$destroy.
Example (Does NOT trigger $destroy)
<div ng-show="isVisible">
<my-directive></my-directive>
</div>
- Fix: Use
ng-ifinstead.
<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
$timeoutor$intervalinside$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.
