$destroy event not triggered on scope

Loading

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?

  1. When a Controller is Destroyed
    • Happens when you navigate away from a view in an ngRoute or UI-Router app.
  2. When a Directive’s Element is Removed
    • Happens when a directive is destroyed due to a UI change (ng-if, ng-repeat, etc.).
  3. When a Scope is Manually Destroyed
    • Happens when $scope.$destroy() is called explicitly.

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 elementDoes 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.

Leave a Reply

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