From d2dc566c57faf8358cff818e8ba41a496c068dc6 Mon Sep 17 00:00:00 2001 From: Andy Gurden Date: Thu, 8 Aug 2013 02:02:03 +0100 Subject: [PATCH] Avoid various memory leaks - Removing window resize handlers on destruction - Stop watching from parent scope on destroy so we can hopefully let go of the grid - Added cleanup to avoid leaking grid through additions to the options object --- src/classes/eventProvider.js | 13 ++++++++++--- src/directives/ng-grid.js | 31 ++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/classes/eventProvider.js b/src/classes/eventProvider.js index 3292e3b21d..644b305bd7 100644 --- a/src/classes/eventProvider.js +++ b/src/classes/eventProvider.js @@ -186,21 +186,28 @@ } // resize on window resize var windowThrottle; - $(window).resize(function(){ + var windowResize = function(){ clearTimeout(windowThrottle); windowThrottle = setTimeout(function() { //in function for IE8 compatibility domUtilityService.RebuildGrid($scope,grid); }, 100); - }); + }; + $(window).on('resize.nggrid', windowResize); // resize on parent resize as well. var parentThrottle; - $(grid.$root.parent()).on('resize', function() { + var parentResize = function() { clearTimeout(parentThrottle); parentThrottle = setTimeout(function() { //in function for IE8 compatibility domUtilityService.RebuildGrid($scope,grid); }, 100); + }; + $(grid.$root.parent()).on('resize.nggrid', parentResize); + + $scope.$on('$destroy', function(){ + $(window).off('resize.nggrid', windowResize); + $(grid.$root.parent()).off('resize.nggrid', parentResize); }); }; // In this example we want to assign grid events. diff --git a/src/directives/ng-grid.js b/src/directives/ng-grid.js index 98980db50e..a9a87068b9 100644 --- a/src/directives/ng-grid.js +++ b/src/directives/ng-grid.js @@ -9,10 +9,27 @@ options.gridDim = new ngDimension({ outerHeight: $($element).height(), outerWidth: $($element).width() }); var grid = new ngGrid($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout, $parse, $http, $q); + + // Set up cleanup now in case something fails + $scope.$on('$destroy', function cleanOptions() { + delete options.gridDim; + delete options.selectRow; + delete options.selectItem; + delete options.selectAll; + delete options.selectVisible; + delete options.groupBy; + delete options.sortBy; + delete options.gridId; + delete options.ngGrid; + delete options.$gridScope; + delete options.$gridServices; + // Plugins should already have been killed as they are children of $scope + }); + return grid.init().then(function() { // if columndefs are a string of a property ont he scope watch for changes and rebuild columns. if (typeof options.columnDefs === "string") { - $scope.$parent.$watch(options.columnDefs, function (a) { + $scope.$on('$destroy', $scope.$parent.$watch(options.columnDefs, function (a) { if (!a) { grid.refreshDomSizes(); grid.buildColumns(); @@ -25,7 +42,7 @@ grid.buildColumns(); grid.eventProvider.assignEvents(); domUtilityService.RebuildGrid($scope, grid); - }, true); + }, true)); } else { grid.buildColumns(); @@ -33,7 +50,7 @@ // Watch totalServerItems if it's a string if (typeof options.totalServerItems === "string") { - $scope.$parent.$watch(options.totalServerItems, function (newTotal, oldTotal) { + $scope.$on('$destroy', $scope.$parent.$watch(options.totalServerItems, function (newTotal, oldTotal) { // If the newTotal is not defined (like during init, set the value to 0) if (!angular.isDefined(newTotal)) { $scope.totalServerItems = 0; @@ -42,7 +59,7 @@ else { $scope.totalServerItems = newTotal; } - }); + })); } // If it's NOT a string, then just set totalServerItems to 0 since they should only be setting this if using a string else { @@ -71,10 +88,10 @@ } $scope.$emit("ngGridEventData", grid.gridId); }; - $scope.$parent.$watch(options.data, dataWatcher); - $scope.$parent.$watch(options.data + '.length', function() { + $scope.$on('$destroy', $scope.$parent.$watch(options.data, dataWatcher)); + $scope.$on('$destroy', $scope.$parent.$watch(options.data + '.length', function() { dataWatcher($scope.$eval(options.data)); - }); + })); } grid.footerController = new ngFooter($scope, grid);