Why isn't $destroy triggered when I call element.remove? - angularjs

I can't figure out why the $destroy event is not triggered in the following example. Can someone explain why it is not triggered, and in what scenarios it will be triggered?
Here's the plunkr: http://plnkr.co/edit/3Fz50aNeuculWKJ22iAX?p=preview
JS
angular.module('testMod', [])
.controller('testCtrl', function($scope){
$scope.removeElem = function(id) {
var elem = document.getElementById(id);
angular.element(elem).remove();
}
}).directive('testDir',[function() {
return {
scope:true,
link: function(scope) {
console.log('in directive');
scope.$on('$destroy', function(){
alert('destroyed');
})
}
}
}]);
HTML
<body ng-controller='testCtrl'>
<div testDir id='test'>I will be removed.</div>
<button ng-click='removeElem('test')'>remove</button>
</body>

The problem is your listening for the $destroy event on the scope, but $destroy is being triggered on the element.
From angular.js source (I'm sure it's documentated on the website somewhere, but I didn't look):
$destroy - AngularJS intercepts all jqLite/jQuery's DOM destruction
apis and fires this event on all DOM nodes being removed. This can
be used to clean up any 3rd party bindings to the DOM element before
it is removed.
Your directive should be (note that I added scope,element, and attrs as link arguments): Also, here is a plunker.
directive('testDir',[function() {
return {
scope:true,
link: function(scope,element,attrs) {
console.log('in directive');
element.on('$destroy', function(){
alert('destroyed');
})
}
};
}]);

I am puzzled on why the $destroy event is not triggered on remove() method.
As per the docs, the $destroy event is triggered in two cases.
Just before a scope is destroyed
Just before an element is removed from the DOM
The purpose being "cleanup". You can listen on the $destroy event and perform necessary cleanups before letting a scope or element be destroyed. ngIf, ngSwitch, ngRepeat and other in-built directives/methods use the $destroy event to perform cleanups.
A best example would be the ngRepeat directive
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
On line 339 you can notice the $destroy event being triggered. You can listen on the event and perform any action just before an item is removed from the list used by ngRepeat.
ngRepeat $destroy Example Plunk -- http://goo.gl/mkozCY

Related

How can I convert custom jquery event listener into angularjs listener?

I have created angularjs component. I wrote a custom event listener in jquery, event is triggered by non-angularjs library.
$( "#myDiv" ).on( "CornerstoneImageRendered", function(e) {
// buisness logic
});
myDiv is a div which is part of angularjs component.
I want to write this listener into angularjs component. How can I do it?
PS: Event link https://github.com/cornerstonejs/cornerstone/wiki/CornerstoneImageRendered-Event
Create a custom directive:
app.directive("myEventListener", function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
elem.on( "CornerstoneImageRendered", function(event) {
// business logic
scope.$eval(attrs.myEventListener, {$event: event});
scope.$apply();
});
}
});
Usage
<div id="myDiv" my-event-listener="onMyEvent($event)">
</div>
JS
$scope.onMyEvent = function(event) {
console.log(event);
};
For more information, see AngularJS Developer Guide - Creating Custom Directives
If you're going to listen on events from an external, non angularjs, library you need to notify angularjs when a change has happend.
One way to do this is by wrapping your logic, which is triggered by the external event, in the $scope.$apply method. The $apply method will
notify angularjs that a change has happend and triggered the digest loop which will sync change from the scope to the view.
Add the event inside the controller and try it like this
$("#myDiv").on("CornerstoneImageRendered", function(e) {
$scope.$apply(function () {
// buisness logic on the scope
});
});
Here's an great artile on the subject
To archive what you want first you need use
angular.element and then you can add event listener

Angularjs destroy is not working

http://plnkr.co/edit/UfQJU661pQR0DMY3c61t?p=preview
I got above code from AngularJs site and only thing I have added a button to delete a Div where we have controller but after delete no destroy method called as I have put alert in Directive and Controller.
element.on('$destroy', function() {
alert('destroy directive interval');
$interval.cancel(stopTime);
});
and
$scope.$on('$destroy', function() {
alert('destroy controller interval');
// Make sure that the interval is destroyed too
$scope.stopFight();
});
please suggest.
Thanks
The main thing to be noticed
When element.remove() is executed that element and all of its children will be removed from the DOM together will all event handlers attached via for example element.on.
It will not destroy the $scope associated with the element.
So you need to manually trigger scope.$destroy();
First get the scope of element:-
var scope = angular.element(document.getElementById("mainDiv")).scope();
Second remove the element from dom:-
$('#mainDiv').remove();
Third destroy scope manually:-
scope.$destroy();
Plunker
You're doing it outside of angular's context.
<button id="btn" onclick="DeleteMainDiv()">DeleteDiv</button>
So in your DeleteMainDiv() function
function DeleteMainDiv() {
alert('Controller div going to remove');
//debugger;
var scope = angular.element(document.getElementById("mainDiv")).scope();
$('#mainDiv').remove();
scope.$destroy();
}
This will trigger the destroy functionality.
But I don't see a need of it. Angular will automatically run the $destroy event handler when the route changes or directive no longer required.
DEMO

Is there a way to detect directive destruction in AngularJS?

I want to use a $timeout in a directive in AngularJS. But I can't find a way in the directive documentation to detect when it is destroyed, in case it happens before my timeout finishes and I need to cleanup the timeout.
Is there an event I can bind to or some built in function (similar to $destroy for controllers) that I can use to detect when my directive will be destroyed? Or am I missing a fundamental concept about directives?
The $destroy event you mentioned can also be used in a directive:
app.directive('myDirective', function() {
return {
link: function(scope) {
scope.$on('$destroy', function() {
// Clean up
});
}
};
});

Directive inside ng-repeat

I'm trying to get a directive inside an ng-repeat to work.. it worked when it was hardcoded in HTML but after switching to an ng-repeat certain aspects of it stopped working.
<div ng-repeat="section in filterSections" filter-tray>
Test {{section.label}}
</div>
I have a module with a controller that emits events:
controller: function($scope, $element) {
this.activateTray = function(trayID) {
$scope.$emit('filterTray::show', {
tray: trayID
});
};
};
I have a directive on the page - it should receive events from the controller. Since switching to using ng-repeat receiving the event has stopped working. It still initialises, it just doesn't listen for the event.
.directive('filterTray', function() {
return {
restrict: 'A',
require: '^filter',
link: function($scope, $element, attrs, filterNavController) {
console.log('this debug statement works');
$scope.$on('filterTray::show', function(e, data) {
console.log('this debug statement never runs');
});
}
};
})
Since adding the repeat has the $scope variable been affected? Perhaps $on isn't listening to the correct thing anymore? Any ideas / tips would be appreciated!
Yes - ngRepeat creates a new scope.
Not sure if events are the right design choice, but if so, use broadcast instead of emit, and the events will reach your directive's scope.
controller: function($scope, $element) {
this.activateTray = function(trayID) {
$scope.$broadcast('filterTray::show', {
tray: trayID
});
};
};
emit shoots events up the scope tree to all ancestors
broadcast reaches all descendants.
When creating a directive that will be reused (this includes ngRepeat) it is best practice to create an isolate scope for your directive. This way you can send the trayID without having to use events. Check out the section on isolate scope in the AngularJS docs on directives here.

Can someone provide an example of a $destroy event for scopes in AngularJS?

Can someone please provide an example of scope's $destroy event? Here is the reference documentation from http://docs.angularjs.org/api/ng.$rootScope.Scope#$destroy
$destroy()
Removes the current scope (and all of its children) from the parent
scope. Removal implies that calls to $digest() will no longer
propagate to the current scope and its children. Removal also implies
that the current scope is eligible for garbage collection.
The $destroy() is usually used by directives such as ngRepeat for
managing the unrolling of the loop.
Just before a scope is destroyed a $destroy event is broadcasted on
this scope. Application code can register a $destroy event handler
that will give it chance to perform any necessary cleanup.
Demo: http://jsfiddle.net/sunnycpp/u4vjR/2/
Here I have created handle-destroy directive.
ctrl.directive('handleDestroy', function() {
return function(scope, tElement, attributes) {
scope.$on('$destroy', function() {
alert("In destroy of:" + scope.todo.text);
});
};
});
$destroy can refer to 2 things: method and event
1. method - $scope.$destroy
.directive("colorTag", function(){
return {
restrict: "A",
scope: {
value: "=colorTag"
},
link: function (scope, element, attrs) {
var colors = new App.Colors();
element.css("background-color", stringToColor(scope.value));
element.css("color", contrastColor(scope.value));
// Destroy scope, because it's no longer needed.
scope.$destroy();
}
};
})
2. event - $scope.$on("$destroy")
See #SunnyShah's answer.

Resources