Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Directive Scope is not getting $destroy called

Been searching for awhile in the archives and on the 'net for an answer, but not really finding one, just bits and pieces. Seems there's a lot of suggested posts, but none of those have the answer.

I have a complex directive which is using scope: true. It's actually the ng-grid latest 2.x version that I'm trying to write cleanup code for, since it just is leaking memory like crazy, and our app is "stuck" with it for now. Here's the plunk that demos the problem. When you click through the grid and check the heap snapshots afterwards, you'll see several "unattached" ngGrid objects. These are only held down by listeners on the directive's scope.

When I'm changing states (using ui.route latest) by clicking through the multiple grids (states), the grid directive's scope IS getting the $destroy event. The handler is working. However, the scope itself is NOT getting $destroy() called on it. I see in the heap snapshot that the directive's scope is still holding onto elements via $$listeners. Also, scope.$$destroyed is not set.

However, scope.prototype IS destroyed. And because it is, I can't even call $scope.$destroy from the directive's $on-$destroy handler, since the proto's $destroy() call changed the definition of $destroy to noop:

ngGridDirectives.directive('ngGrid', ['$compile', '$filter', '$templateCache', '$sortService', '$domUtilityService', '$utilityService', '$timeout', '$parse', '$http', '$q',
    function($compile, $filter, $templateCache, sortService, domUtilityService, $utils, $timeout, $parse, $http, $q) {
  var ngGridDirective = {
    scope: true,
    compile: function() {
      return {
        pre: function($scope, iElement, iAttrs) {
          $(iElement).on('$destroy', function cleanupElementNgGrid() {
              $timeout(function() {
                $scope.$destroy();
                $scope.destroyedByForce = true;
                console.log("I destroyed it.");
              },4000);
          });
        ...

Setting scope.$$listeners = {} with a $timeout of 2000 (to give time for the directive to finish cleaning up in my on-$destroy listener seems to work , but it feels wrong to munge with the internals, and sometimes it's not long enough for the browser to finish the cleanup. It's just a work-around/kludge.

I also tried this:

So what is preventing my directive's scope from getting $destroy() automatically called on it?

At first, I thought it was because we use scope: true in the directive options, since the proto scope seems to have been destroyed. So I wrote a plunk to try that theory out. But with this plunk, the directive's scope is properly destroyed and no objects are leaked. Very surprising, actually. But I don't use the same view nesting I have in the first plunk; however I doubt that's it. Controller scope still gets wiped when the views change. I still have a watcher on an internal object. So I would think I'd see a similar dynamic. But it appears that $destroy is really called on that internal scope.

Any ideas about what would prevent a directive scope from getting $destroy() called on it? IT seems related to the scope: true bit, but I can't quite grok Angular internals enough to say why.

Thank you in advance,

Jesse


Update: OK, I'll post an update to this issue.

To the best of my ability, it seems what's happening is that the main scope for the element (not the semi-isolate scope for the grid directive) gets $destroy()d. However, the child scope which is using that scope as the prototype is most likely bailing out of the $destroy method early, because self.$$destroyed is true (prototypal inheritance). So listeners and watchers do not get cleared.

I probably could update the directive to be fully isolate in scope, but I don't have time to sort out the implications of that.

I also found it's more than just $destroy. The $scope itself has a LOT of functions defined on it by the directive that form closure around internal vars that hold a lot of memory.

So, I wrote a service that supplies additional cleanup that is easily injected into the questionable grid directive. I have a plunk demo'ing it, but I can't post it in the main piece due to my low reputation :). Now, you can switch grids all you like and none but the current grids will remain on the heap.

Hope this helps someone going forward,

J

like image 456
jessewolfe Avatar asked Oct 05 '14 00:10

jessewolfe


People also ask

What is$ scope AngularJS?

The $scope in an AngularJS is a built-in object, which contains application data and methods. You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML).

What is the number of scopes allowed in an AngularJS application?

Each AngularJS application has exactly one root scope, but may have any number of child scopes.

What is the use of scope apply in AngularJS?

In AngularJS, $apply() function is used to evaluate expressions outside of the AngularJS context (browser DOM Events, XHR). Moreover, $apply has $digest under its hood, which is ultimately called whenever $apply() is called to update the data bindings.

How many child scopes can an AngularJS application have?

Scope Hierarchies Every angularJS application can have only one root scope as it is the parent scope. But it can have more than one child scope. New scopes can be created by directives which are added to parent scope as child scope.


1 Answers

Don't know if this is better suited as a comment or not. I recently worked on a large project which used ng-grid + angularjs and we had all the issues that you've encountered: memory leaks gallore. We found that indeed not everything was being cleaned nicely and scopes and watches were leaked all over the place.

We tried to add some custom logic to try and do a better cleanup but it didn't solve our problem 100%. Our issues were also accentuated by horizontal scrolling + virtualization on ng-grid so it might be useful to keep that in mind as well (if I remember correctly they were planning on fixing this in a future version).

like image 194
Stefan Ch Avatar answered Oct 19 '22 09:10

Stefan Ch