0

tl;dr

Application loads modules, refreshes every X minutes, does not destroy module instances on reloading. $scope.$on('$destroy') does not get called. Modules DDoS API and slow down application. Manual $broadcast from applicaton controller to trigger a destroy (code below). How to manually unload a controller instance when $scope.$destroy() gives "current is null" error?

More detailed explanation

I'm currently working on a dashboard style application which uses angularjs modules as widgets. These are being dynamically loaded using ocLazyLoad to reduce application load times. All content comes from a JSON API.

The dashboard reloads it's widget every 30 minutes (update code below), also rebuilding the view. When this happens, the existing widgets do not get destroyed and remain in the memory. Meaning every X minutes they are set to update also continues to happen. Thus when letting the application run for 2 hours, there's 4 instances of the widgets asking for data every X seconds. When the dashboard reloads its widgets, there's no destroy event getting called on the old instances as these are never destroyed.

I've partially fixed this by having the dashboard controller broadcast a destroy event so the running controllers clean up their $timeouts, however this does obviously not remove the loaded instances of the widgets from the memory. Trying to call $scope.$destroy() results in a current is null error, and using the element remove code below.

The question is, how else can I destroy these controllers?

code blocks

Destroy broadcast receiver

$scope.$on("destroy", function () {
    console.log("RMA Widget scope received destroy event. Destroying RMA widget");
    $scope.$destroy();
});

Remove module (widget) elements

document.querySelectorAll(".widget").forEach((el) => {
    el.remove()
});

Loading modules (widgets). $scope.widgets is used in ng-repeat in the body.

$scope.widgets         = [];
for (let i = 0; i < rqData.data.data.widgets.length; i++) {
    const widget = rqData.data.data.widgets[i];
    $ocLazyLoad.load('app/' + widget.type + '/module.js');
    $scope.widgets.push({
        directive   : widget.type + '-widget',
        module      : 'app/' + widget.type + '/module.js',
        view        : 'app/' + widget.type + '/view.html',
        data        : Object.assign(widget.widget_id,
            {item_id : widget.id, content : widget.content}),
    });
}
$scope.timeout.compile = $timeout($ctrl.compileView, 3000);
Community
  • 1
  • 1
ZeroThe2nd
  • 1,652
  • 1
  • 12
  • 15
  • I advice you to create small example plunk with your problem. First see if problem exist if you remove lazy load, etc. Adding akward code like you post here wont lead you anywhere. – Petr Averyanov Jun 27 '18 at 16:06

1 Answers1

0

If you are using ng-repeat it should be enough to delete the variable $scope.widgets and set it again. And i mean really delete the var with delete $scope.widgets Maybe like this:

function set_widgets(){
    if($scope.widgets !== undefined){
        delete $scope.widgets;
    }
    $scope.widgets         = [];
    for (let i = 0; i < rqData.data.data.widgets.length; i++) {
        const widget = rqData.data.data.widgets[i];
        $ocLazyLoad.load('app/' + widget.type + '/module.js');
        $scope.widgets.push({
                                directive   : widget.type + '-widget',
                                module      : 'app/' + widget.type + '/module.js',
                                view        : 'app/' + widget.type + '/view.html',
                                data        : Object.assign(widget.widget_id,
                                                            {item_id : widget.id, content : widget.content}),
                            });
    }
}

Edit: All $intervals and $timeouts inside the widgets controller need to be stopped by yourself that isn't something that is bind to the scope of the controller

MrWook
  • 348
  • 3
  • 11
  • I've added `delete $scope.widgets` before setting the variable as empty array again in the function, however this doesn't seem to work. The previously created (before the update) widgets' controllers still respond to a ping broadcast with their own id. – ZeroThe2nd Jun 27 '18 at 12:36
  • Partially. I've had an `$scope.$on('$destroy')` call in every module that stopped the timeouts, however these were never called. I've set the dashboard controller to send a broadcast event that triggers the destroy events now. That did solve the issue, however the old instances keep being present in the memory (testing this by making every controller instance respond to a "ping" broadcast with their own `$scope.$id`. – ZeroThe2nd Jun 27 '18 at 14:12
  • Not sure who upvoted this... 3 lines added actually do nothing at all – Petr Averyanov Jun 27 '18 at 16:08