Search code examples
angularjscontrollerangularjs-scopeondestroy

Scope not being removed


Using angular 1.2.9

Explanation of what is working: I have a list on the left of the webpage where a user can press + button to append a view to the .viewSpace.

When the user presses +

angular.element(document.getElementById('viewSpace')).append($compile("<div my-graph></div>")(scope));

is called. my-graph is a directive that uses a template which looks like

<div ng-controller="MyGraphController" id="{{viewId}}">
...
</div>

viewId being and created on instantiation, that id is passed to a service which keeps track of views and broadcasts changes so that the left view can know to update the list with a new - button to remove elements that have just been added. The remove directive looks like:

app.directive("removeView", function($compile){
    return function(scope, element, attrs){
        element.bind("click", function(){
            var id = '#'+attrs.removeView; // id of view to remove when clicked
            console.log('Removing', id);
            angular.element(id).parent().remove();
            //angular.element(document.getElementById(attrs.tseRemoveView)).remove();
            $compile(".viewSpace");
        });
    };
});

and graph controller cconsists of a destroy function.

chartApp.controller('MyGraphController', function ($scope, $interval, controllerService) {
    $scope.cid = controllerService.getCurrentControllerId();
    $scope.viewId = controllerService.createViewId($scope.cid);
    controllerService.bindToViewList($scope.viewId, "Linear Graph");// for view communication.

    $scope.$on("$destroy", function() { // never gets called.
        console.log('destroying cid', $scope.cid);
        resetGraph(); // the intervals will persist after controller instance is destroyed, so take care of them onDestroy
        controllerService.removeBindToViewList($scope.viewId, "Linear Graph");
    });
});

Problem: the graph disappears from the DOM but the scope remains, and the destroy function never gets called. Any ideas?

chartApp.directive('myGraph', function($rootScope, $window) {
    return {
        restrict: 'EA',
        templateUrl: 'myGraph.tpl.html',
        scope: {}, 
        link: function(scope, elem, attrs) {
            elem.on('$destroy', function(){// does get called though
                alert('elem destroyed');
            });
        }
    };
});

controllerService functions for sharing view data accross scopes:

this.bindToViewList = function(id, viewType){
        console.debug('bindToViewList', id, viewType);
        if(viewType in views){
            views[viewType].push(id);
        }else{
            views[viewType]=[id];
        }
        broadcastViewUpdate();
    }

    this.removeBindToViewList = function(id, viewType){
        console.debug('removeBindToViewList', id, viewType);
        var index = views[viewType].indexOf(id);
        views[viewType].splice(index, 1);
        broadcastViewUpdate();
    }

Solution

  • The short answer is, $destroy is not automatically called by Angular when an element is removed. You must call it explicitly (https://docs.angularjs.org/api/ng/type/$rootScope.Scope):

    $destroy() must be called on a scope when it is desired for the scope and its child scopes to be permanently detached from the parent and thus stop participating in model change detection and listener notification by invoking.

    More info: http://www.bennadel.com/blog/2706-always-trigger-the-destroy-event-before-removing-elements-in-angularjs-directives.htm

    Also, your design seems to be relying heavily on the DOM instead of connecting your models and letting Angular handle the interaction with the DOM. It seems like you could have a single directive for your viewspace that renders and array of views using ng-repeat and then call splice() to remove a view when the user clicks the - button. Angular would handle the DOM and you wouldn't have to do any jQuery since angular would remove anything from the DOM that was removed from the array.