Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Memory Leak with ng-repeat using custom objects (w/simple PLUNKR)

(simple plunkr demo here)

SUMMARY:

There is a leak using ng-repeat after the 2nd wave iterating over an 'array' of custom objects like this :

    <div ng-repeat="d_sampleObject in mySampleObjects">
        {{d_sampleObject.description}}
    </div>

Memory profile reveals an extra 'd_sampleObject' left over and not de-referenced. More details (via a controller and an injected service) below. A simple demonstration also in the provided plunkr link. Any thoughts and help greatly appreciated in advance!

NOTE: 'mySampleObjects' is an array of the following instances:

        ml.MySampleObject = function (id) {

            this.id = id;
            this.description = 'this is object #:' + ' '+id;
        }

DETAILS:

I have a custom object model that reflects the business domain objects that we utilize in our AngularJS app. I have found that when I pass an instance of one of my custom objects to ng-repeat, a reference is kept to it (I think by Angular) and memory is not freed. This happens on the second 'wave' (click on 'refresh') of the ng-repeat as it iterates, again, over its array of objects. This leak is exposed in my Profile tests (in Chrome) . Here is a short example in plunkr. Click on 'refresh' button once (or more) to see the extra 'd_sampleObject' object instance that is leaked (in Chrome Profile Inspection). Note, the 'd_sampleObject' name is only used when passed to ng-repeat. I have included screenshots of the extra object instance ('d_sampleObject') that is being leaked further below. Why is there a leak and how can it be avoided?

(Note, I have found if I don't iterate over my object collection (JS array) thru an object but rather thru a primitive index ('integer'), there is no leak. The leak seems to only happen when I use an object reference as a result of ng-repeat iterations)

SIMPLE HTML:

<!DOCTYPE html>
<html ng-app="memoryleak">

    <head>
    <meta charset="utf-8" />
    <title>Memory Leak Test</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script data-require="[email protected]" src="https://code.angularjs.org/1.3.13/angular.min.js" data-semver="1.3.13"></script>

    <script src="app.js"></script>
    <script src="dataservice.js"></script>    
  </head>

  <body ng-controller="MainCtrl">


    <div ng-repeat="d_sampleObject in mySampleObjects">
        {{d_sampleObject.description}}
    </div>

    <br>

    <button ng-click="redo()">Number of refreshes: {{numRedos}}!</button>
  </body>

</html>

SIMPLE APP.JS

(function(ml) {
    var app = angular.module('memoryleak',['servicemodule']);

    app.controller('MainCtrl', ['$scope', 'dataservice', function($scope, dataservice) {

        $scope.redo = function () {

            $scope.numRedos++;

            $scope.mySampleObjects = dataservice.myObjectCollection;

            dataservice.redo();
        }

        $scope.redo();

    }]);

}(window.MEMLEAK = window.MEMLEAK || {}));

SIMPLE dataservice.js

(function(ml) {

    'use strict';

    var serviceModule = angular.module('servicemodule',[]);

    serviceModule.factory('dataservice', ['$rootScope', '$http',
                                        function ($rootScope, $http) {

        this.myObjectCollection = [];

        this.redo = function () {

                this.numRedos++;

            // that.myObjectCollection = [];
            this.myObjectCollection.length = 0;

            for (var i = 0; i < 10; i++) { 
                var sampleObject = new ml.MySampleObject(i);

                that.myObjectCollection.push(sampleObject);
            }   


            sampleObject=null;        

        }

        ml.MySampleObject = function (id) {

            this.id = id;
            this.description = 'this is object #:' + ' '+id;
        }  

        return this;   //return the entire service to make methods accessible to dependents

    }]);

}(window.MEMLEAK = window.MEMLEAK || {}));

SCREENSHOT 1: (FIRST PAGE LOAD--there are 10 'mySampleObjects' created) SCREENSHOT 1:  (FIRST PAGE LOAD--there are 10 'mySampleObjects' created) SCREENSHOT 2: (CLICKED ON REFRESH--there is an 11th mySampleObject created/leaked with a reference to the instance name of 'd_sampleObject' passed to ng-repeat.) (CLICKED ON REFRESH--there is an 11th mySampleObject created/leaked with a reference to the instance name of 'd_sampleObject' passed to ng-repeat.)

like image 357
MoMo Avatar asked Feb 23 '15 21:02

MoMo


1 Answers

There is acknowledgement by the AngularJS folks that this is indeed a bug in the framework. A fix and pull request has been posted.

I have also asked what the timeframe is for a formal fix.

like image 107
MoMo Avatar answered Nov 09 '22 08:11

MoMo