I've seen a lot of directive examples including those by the AngularUI team where they don't appear to do any cleanup.
Here's an example from their ui-date directive which creates a jQuery datepicker. (source)
element.on('blur', function() { ... });
They placed an event handler on the element, but at no point do they ever unbind the event. I would have expected there to be code present such as:
var namespace = ".uiDate";
element.on('blur' + namespace, function() { ... });
element.on("$destroy" + namespace, function ()
{
element.datepicker("destroy"); //Destroy datepicker widget
element.off(namespace); //Unbind events from this namespace
});
So this makes me wonder if there's something I don't understand. Wouldn't what they are doing cause a memory leak in situations where the element w/ this directive is created and destroyed over and over?
What am I missing?
removeChild(b); b = null; // A reference to 'b' no longer exists // Therefore the element and any event listeners attached to it are removed. However; if there are references that still point to said element, the element and its event listeners are retained in memory.
To remove all event listeners from an element: Use the cloneNode() method to clone the element. Replace the original element with the clone. The cloneNode() method copies the node's attributes and their values, but doesn't copy the event listeners.
$destroy. Broadcasted when a scope and its children are being destroyed. Note that, in AngularJS, there is also a $destroy jQuery event, which can be used to clean up DOM bindings before an element is removed from the DOM.
Yes ideally you should clean up any event handlers that are attached to elements other than the element linked to the directive.
Fore example if in your directive you have a window resize function to modify the element of the directive you will need to remove the window resize event when the directive is destroyed.
here's an example directive I had to build and you can see I had to unbind the event handlers attached outside of the scope of the directive:
lrApp.directive('columnArrow',function($timeout){
return {
restrict : 'A',
scope : {
active : '=columnArrow'
},
link: function($scope, elem, attrs, controller) {
$scope.$watch('active',function(){
$timeout(function(){
adjust();
},0);
});
$(window).resize(adjust);
elem.parents('.column-content').scroll(adjust);
$scope.$on('$destroy', function () {
elem.removeClass('hide');
elem.parents('.column-content').unbind('scroll',adjust);
$(window).unbind('resize',adjust);
});
function adjust(e) {
if($scope.active) {
var parentScroll = elem.parents('.column-content');
var parent = elem.parent();
var visible = inView(parentScroll[0],parent[0]);
if(!visible) {
elem.addClass('hide');
} else {
elem.removeClass('hide');
}
var offset = parent.offset();
var w = parent.outerWidth();
var h = (parent.outerHeight() / 2) - (elem.outerHeight() / 2);
elem.css({'top':offset.top + h,'left':offset.left + (w + 5)});
}
};
}
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With