Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are scopes not destroyed even after $destroy is triggered?

I have made a directive that, when clicked, creates a dialog that is appended to the body using jQuery. The problem is that when the dialog is closed, the scopes are never properly cleaned up. As shown in the picture below 167 ChildScopes are preserved. Which matches the amount of items in the dialog which includes the ng-repeat directive.

enter image description here

I attempted to create an extremely simple version of the scenario on Plnkr. To my surprise the scopes ARE in fact being removed on each close in the Plnkr. So something, somewhere in production is causing the scopes to stay alive even after $destroy has been called.

link: ($scope, $element, $attr) ->
  $element.on 'click', () ->
      $scope.$apply () ->
        child = $scope.$new()
        template = """<span ng-controller="ListCtrl">...List dialog things...</span>"""
        compiledTemplate = $compile(template)(child)
        container = containers.createDialogContainer($element)
        container.append(compiledTemplate)

        #cleanup
        $scope.closeWidget = () ->
          container.trigger("container_close")
          return

        container.on "container_close", ()->
          child.$apply () ->
            child.$destroy()
          return

So here is my question:

What can cause a scope to stay alive even after $destroy has been called, triggered and garbage collection performed?

For obvious reasons I cannot show you our production code. However the directive in the Plnkr matches the one im debugging sufficiently.

like image 408
CodePrimate Avatar asked Feb 18 '14 13:02

CodePrimate


1 Answers

In general, a scope (or any other JS object) can not be cleaned up by the GC if it is still accessible by another JS object.

In practice, in an Angular project that also uses JQuery, this is most likely caused by:

  • an Angular service, controller, scope or some other object still having a reference to your scope object
  • a reference to your object still exists through a DOM element, probably through an event listener. The DOM element might not be GC-able itself because it is still in the cache of JQuery

Your example, for instance, creates a memory leak.

From your code:

 $scope.removeDialog = () ->
    console.log "closing"
    child.$destroy()
    $('.listshell').remove()
    return

You do not set child to null so $scope.removeDialog still has access to the scope object referenced by the child variable. Therefore, this object can not be GC'ed.

NB: It seems to me that it would be more appropriate to put removeDialog on the child scope. Now your example only works because the child scope is not isolated.

like image 88
Pieter Herroelen Avatar answered Oct 05 '22 18:10

Pieter Herroelen