Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak (Detached Dom Trees) #193

Open
ghost opened this issue May 17, 2014 · 4 comments
Open

Memory leak (Detached Dom Trees) #193

ghost opened this issue May 17, 2014 · 4 comments

Comments

@ghost
Copy link

ghost commented May 17, 2014

Hi,

I am creating a webapp where the users can access to certain line charts. These line charts are organized in different routes. The issue that I have is that, it seems that, after visiting one of this charts, once the user has moved to a different route, the memory used to display the chart is not released. So if the user keeps visiting different charts for a while, or even if the user just keeps visiting the same chart, the webapp ends up consuming all the memory available in the system.

Using the profile tool in the Chrome Dev Tools, I have seen that there are "Detached Dom Tree" elements in my app that contain stuff like "SVGCircleElement", "SVGSVGElement", "SVGLineElement". Do I have to listen for $destroy() in order to somehow manually delete this elements?

I am quite new to Angularjs and js in general, so probably there is some bug somewhere in my app. But I have already made the app as simple as possible and still cannot find how to avoid this issue.

I just have a module like:

var showTasks = angular.module('showTasks', ['nvd3ChartDirectives']);

With a controller:

showTasks.controller('showTasksController', [ '$scope', '$http', '$routeParams', function($scope, $http, $routeParams) {

I fetch the data:

$http.post('/api/task', $scope.task_id).success(function(data, status, headers, config) {

And I store the pairs of "key" + "values" in an array (each page can have multiple line charts). Something like:

$scope.task = data
for(var i=0; i < $scope.task.result.evaluations.length; i++) {

    $scope.newData = [
        {
            "key": $scope.task.result.evaluations[i]['curve_name'],
            "values": $scope.task.result.evaluations[i]['curve_points']
        }
    ];


    $scope.data_for_nvd3.push($scope.newData);
}

And then I display the charts:

<li ng-repeat="evaluation in task.result.evaluations">
    <nvd3-line-chart
        color="colorFunction()"
        height="300"
        width="500"
        data="data_for_nvd3[$index]"
        margin="{left:80,top:50,bottom:50,right:10}"
        showXAxis="true"
        showYAxis="true"
        interactive="true"
        tooltips="true"
        tooltipcontent="toolTipContentFunction(task.result.evaluations[0].x_axis_name, task.result.evaluations[0].y_axis_name)"
        xAxisLabel={{task.result.evaluations[0].x_axis_name}}
        yAxisLabel={{task.result.evaluations[0].y_axis_name}}
        showLegend="true"
        legendcolor="colorFunction()"
        interpolate="basis">
        <svg></svg>
    </nvd3-line-chart>
</li>

Has anybody experienced a similar problem? Any hint about what could be going wrong?

Thanks a lot for your help!

@ghost
Copy link

ghost commented May 25, 2014

The below piece can take a performance hit

$scope.task = data
for(var i=0; i < $scope.task.result.evaluations.length; i++) {

$scope.newData = [
    {
        "key": $scope.task.result.evaluations[i]['curve_name'],
        "values": $scope.task.result.evaluations[i]['curve_points']
    }
];

$scope.data_for_nvd3.push($scope.newData);

}

I would process data first before attach to $scope.

@owenallenaz
Copy link

I had a similar experience using nvd3 raw not related angular. What I found was that nvd3 keeps an nv.graphs array of all functions passed to nv.addGraph. When I reached an event that warranted "unloading" my graph (such as loading a new page), I was able to brute force it by calling nv.graphs = [] and it would clear the array. You could be more refined and iterate through and remove the specific graph function you want, but this solved the detached dom node memory leak for me.

@Pi-r-
Copy link

Pi-r- commented Sep 2, 2014

I also have a similar issue and opened a stakoverflow question here and a jsfiddle to demonstrate the issue.

Basicaly, I'm doing some cleanup on the $destroy event of the controller and on the routeChange event, but it seems we need more than that:

$scope.$on('$destroy', function() {
  d3.select( '#exampleId' ).remove();
  d3.select( '#exampleId2' ).remove();
});

myApp.run(function($rootScope, $templateCache) {
  //try to clear unused objects to avoid huge memory usage
  $rootScope.$on('$routeChangeStart', function(event, next, current) {
    if (typeof(current) !== 'undefined'){
      //destroy all d3 svg graph
      d3.selectAll('svg').remove();
      nv.charts = {};
      nv.graphs = [];
      nv.logs = {};
    }
  });
});

@ali-bugdayci
Copy link

@Pi-r- setting the chart and graph data to empty values did not work for me. I believe it is not garbage collected since the data is still somehow referenced in the html.

I tested my environment with the google profiler (comparing the heap snapshots).

The following code does not leak the SVG elements. But still the Array data is somehow leaked:

  $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
    angular.element(document.body.querySelectorAll('.nvd3')).remove();

My leak reduced from 5 MB to 2 MB.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants