var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider
.when('/home', {
templateUrl: 'home.html',
controller: 'HomeController as vm',
resolve: {
dsData: ['dataService',
function(dataService) {
return dataService.init();
}
]
}
}).when('/data', {
templateUrl: 'data.html',
controller: 'DataController as vm',
resolve: {
dsData: ['dataService',
function(dataService) {
return dataService.init();
}
]
}
})
}
]);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.21/angular.js" data-semver="1.2.21"></script>
<script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://code.angularjs.org/1.2.14/angular-route.js"></script>
<script src="app.js"></script>
<script src="directives.js"></script>
<script src="controllers.js"></script>
<script src="data.srv.js"></script>
<script src="security.srv.js"></script>
<script src="ngRefresh.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
</head>
<body ng-controller="MainCtrl" ng-app="app">
<p>Hello {{name}}!</p>
<p>
<a href="#home">Home</a>
<a href="#data">Data</a>
</p>
<div id="ng-app-view" data-ng-view=""></div>
<div id="memory-leak"><a href="#">Memory Leak Test</a></div>
<i><ul>
<li>Click Home</li>
<li>Click Data</li>
<li>Repeat several times</li>
<li>Click Memory Leak test to see all instances of Home Controller alive in console.</li>
</ul>
The purpose of this is to test ngRefresh.js to clear out memory, as in a complete browser refresh.
Clearing out the memory from the top-down approach, rather than spending hours tracking down errors.
<b>Final answer may require a recursive node traversal to free object references.</b>
</i>
</body>
</html>
/* Put your css in here */
<p>Welcome to the home page</p>
<input ng-model="data.myText">
<div ng-show="showMe()">Your text is: {{data.myText}}</div>
<div test-dir></div>
<p>Welcome to the data page</p>
<ul>
<li ng-repeat="item in dsData">{{item}}</li>
</ul>
<div test-dir></div>
<table id="example" class="display" cellspacing="0" width="100%">
</table>
<script>
$(document).ready(function() {
var dt = $('#example').dataTable();
dt.fnAddData({{dsData}});
} );
</script>
var serviceId = 'dataService';
angular.module('app').service(serviceId, ['$http', dataService]);
function dataService($http) {
var init = function() {
console.log("[DataService] init()");
return $http.get("myData.txt");
},
service = {
init: init
};
return service;
}
apples, oranges, bananas, pineapple, strawberries, grapes, cucumber
angular.module('app').controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
var something = "";
for (var x = 0; x < 65000; x++)
something += x.toString();
var homeControllerInstance = 0;
angular.module('app').controller('HomeController', ['$scope', 'dsData', 'securityService',
function($scope, dsData, securityService) {
var myInstance = homeControllerInstance++;
$scope.dsData = dsData.data;
$scope.data = {};
$scope.data.myText = "Hello World!";
$scope.data.counter = 0;
$scope.data.leak = 0;
$scope.$on("$destroy", function() {
console.log("[HomeController " + myInstance + "] destroy()");
delete $scope.dsData;
});
//*** CREATING MEMORY LEAK ***
$("#memory-leak").on('click', function() {
console.log("[HomeController " + myInstance + "] click() " + $scope.data.counter);
$scope.data.leak.push($scope.data.counter++);
});
function useSomeMemory() {
var memory = [];
for (var i = 0; i < 1000; i++) {
memory.push(something);
}
$scope.data.leak = memory;
}
$scope.showMe = function() {
console.log("[HomeController " + myInstance + "] showMe()");
return $scope.data.myText.length > 0 && securityService.validate();
}
useSomeMemory();
// *** OPTIONAL: CREATE DETACHED DOM ELEMENTS **
function detachDom() {
var b = 1000;
while (b--) {
var div = document.createElement("div");
div.className = "mydiv";
div.innerHTML = "<span></span>";
document.body.appendChild(div);
}
var parents = $("span").parent("div");
$("span").remove();
}
//detachDom();
}
]);
angular.module('app').controller('DataController', function($scope, dsData) {
$scope.dsData = dsData.data.split(",");
$scope.$on("$destroy", function() {
console.log("[DataController] destroy()");
delete $scope.dsData;
});
});
angular.module('app').directive('testDir', function () {
return {
template: '<p>Testing directive</p>'
}
});
var serviceId = 'securityService';
angular.module('app').service(serviceId, ['$http', securityService]);
function securityService($http) {
var init = function() {},
validate = function() {
console.log("[SecurityService] validate()");
return true;
},
service = {
init: init,
validate: validate
};
return service;
}
(function() {
'use strict';
app.run(['$rootScope',
function($rootScope) {
var ngViewDivId = "ng-app-view",
garbageCollection = [];
// from http://stackoverflow.com/questions/1248302/javascript-object-size
function roughSizeOf(object) {
var objectList = [];
var stack = [object];
var bytes = 0;
while (stack.length) {
var value = stack.pop();
if (typeof value === 'boolean') {
bytes += 4;
} else if (typeof value === 'string') {
bytes += value.length * 2;
} else if (typeof value === 'number') {
bytes += 8;
} else if (
typeof value === 'object' && objectList.indexOf(value) === -1
) {
objectList.push(value);
for (var i in value) {
stack.push(value[i]);
}
}
}
return bytes;
}
function refreshApp() {
var host = document.getElementById(ngViewDivId);
if (host) {
var mainDiv = $("#" + ngViewDivId);
$("body").off();
mainDiv.empty();
angular.element(host).empty();
}
}
function garbageCollect() {
angular.forEach(garbageCollection, function(item) {
// attempting to free scopes
console.log("[Garbage Collection] Freeing item of size (" + roughSizeOf(item) + " bytes).");
item = null;
// do more to free scope?
});
garbageCollection = [];
}
function logRouting(type, current, next) {
var curCtrl = (current) ? current.$$route.controller : " start ",
curSize = (current) ? roughSizeOf(current.scope) : 0,
nxtCtrl = (next) ? next.$$route.controller : " start ",
nxtSize = (next) ? roughSizeOf(next.scope) : 0;
console.log("[Routing " + type + "] from " + curCtrl + " (" + curSize + " bytes) to " + nxtCtrl + " (" + nxtSize + " bytes)");
}
$rootScope.$on('$routeChangeStart',
function(event, next, current) {
logRouting("start", current, next);
refreshApp();
if (current) {
garbageCollection.push(current.scope);
}
}
);
$rootScope.$on('$routeChangeSuccess',
function(event, next, current) {
logRouting("success", current, next);
garbageCollect();
}
);
}
]);
}());