Memory Leak: Remaining elements in cache and data_user in AngularJs - javascript

I create elements (some are SVG Tags, some are simple HTML) with ng-repeat. On changes of the data model - an object that is reset on arrival of new data - there are always elements left behind as detached DOM elements. They are held like this:
The Elements are part of data_user which seems to be part of jquery. This problem occurs at several places on change of data. It seems that watchers are the problem, since they are keeping reference to their expression.
The elements are created e.g. like this:
.directive('svgGraphic', ['$compile', function ($compile) {
return {
restrict: 'E',
replace: false,
link: function (scope, element, attrs) {
var svgData = scope.model.getAttribute("svgGraphic");
var svgDomElement = $(svgData.svg);
scope.layers = svgData.layers;
svgDomElement.append('<svg-layer ng-repeat="layer in layers"></svg-layer>');
element.append($compile(svgDomElement)(scope));
scope.$on("$destroy", function() {
scope.$$watchers = null;
scope.$$listeners = null;
})
}
};
}])
A workaround is to manually delete watchers and listeners as you can see above - what is no good solution I think!
When new data from the server arrives, it is set like this:
$scope.model = model;
$scope.$digest();
Is it a problem to just replace the model data?
Is there any idea how it can happen that angular does not remove listeners on old elements? Angular should delete all the watchers when ng-repeat receives new data and rebuilds all elements.

I found the same issue. I created a Watcher Class, so then with the profiler I am able to count the Watcher instances. I saw that the instances continue increasing while I navigate the app, some of the instances are retained by the data_user cache :(.
Also I fixed the delete of childScopes watcher amd added some metadata to scopes, like a list of childScope.
Here is the angular code that I changed (only the functions that I changed). I hope this help you to found the error, I am still fighting with it :)
function $RootScopeProvider() {
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
function($injector, $exceptionHandler, $parse, $browser) {
var watcherCount = 0;
function Watcher(listener, initWatchVal, get, watchExp, objectEquality, scope) {
this.fn = isFunction(listener) ? listener : noop;
this.last = initWatchVal;
this.get = get;
this.exp = watchExp;
this.eq = !!objectEquality;
this.scope = scope;
this.id = watcherCount++;
}
Watcher.prototype = {
constructor: Watcher
}
function Scope() {
this.$id = nextUid();
this.$$phase = this.$parent = this.$$watchers =
this.$$nextSibling = this.$$prevSibling =
this.$$childHead = this.$$childTail = null;
this.$root = this;
this.$$destroyed = false;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$isolateBindings = null;
this.childsScopes = [];
}
Scope.prototype = {
constructor: Scope,
$new: function(isolate, parent) {
var child;
parent = parent || this;
if (isolate) {
child = new Scope();
child.$root = this.$root;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
this.$$ChildScope = function ChildScope() {
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$id = nextUid();
this.$$ChildScope = null;
};
this.$$ChildScope.prototype = this;
}
child = new this.$$ChildScope();
}
//window.scopes = window.scopes || {};
//window.scopes[child.$id] = child;
this.childsScopes.push(child);
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if (parent.$$childHead) {
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else {
parent.$$childHead = parent.$$childTail = child;
}
// When the new scope is not isolated or we inherit from `this`, and
// the parent scope is destroyed, the property `$$destroyed` is inherited
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if (isolate || parent != this) child.$on('$destroy', destroyChild);
return child;
function destroyChild() {
child.$$destroyed = true;
child.$$watchers = null;
child.$$listeners = {};
//child.$parent = null;
child.$$nextSibling = null;
child.$$childHead = null;
child.$$childTail = null;
child.$$prevSibling = null;
child.$$listenerCount = {};
if (child.$parent) {
var index = child.$parent.childsScopes.indexOf(child);
child.$parent.childsScopes.splice(index, 1);
}
console.log("Destroying childScope " + child.$id);
}
}
$destroy: function() {
// we can't destroy the root scope or a scope that has been already destroyed
if (this.$$destroyed) return;
var parent = this.$parent;
console.log('Destroying Scope '+ this.$id);
//delete window.scopes[this.$id];
this.$broadcast('$destroy');
this.$$destroyed = true;
if (this === $rootScope) return;
for (var eventName in this.$$listenerCount) {
decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
}
// sever all the references to parent scopes (after this cleanup, the current scope should
// not be retained by any of our references and should be eligible for garbage collection)
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
// Disable listeners, watchers and apply/digest methods
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
this.$$listeners = {};
// All of the code below is bogus code that works around V8's memory leak via optimized code
// and inline caches.
//
// see:
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
this.$$childTail = this.$root = this.$$watchers = null;
}
}];
}

Remove the cached SVG data.
Directive Code
(function () {
"use strict";
angular.module('ApplicationModule').directive("ngD3SvgRefreshDirective", ["$rootScope", function ($rootScope) {
return {
restrict: 'AE',
templateUrl: 'pages/directive/D3SvgMyRefereshDirective.html',
scope: {
svgData: "=svgData",
reloadSvg: "=reloadSvg",
},
controller: function ($scope, $sce, $rootScope) {
$scope.$watch('reloadSvg', function (nu, old) {
$scope.init();
}, true);
$scope.init = function () {
if ($scope.svgData && $scope.svgData.layoutURI) {
console.log("data");
if ($scope.svgData) {
// if data is present ###
var svgDiv = angular.element(document.querySelector('#svgDiv')).html("");
d3.xml($scope.svgData.layoutURI, "image/svg+xml", function (xml) {
svgDiv.append(xml.documentElement);
$scope.styleSVGPathsNaddCallBack();
});
}
}
else {
// if data is not present ###
var svgDiv = null;
// selecting all the svg div which already present in browser ###
var svg = d3.select("svg");
svg.selectAll("*").remove();
console.log(svg);
}
};
$scope.styleSVGPathsNaddCallBack = function () {
var svgObject = d3.select('#svgDiv').select('svg');
svgObject.attr("width", "100.0%");
var objColor;
if (true) {
// your requirement logic
}
};
}
};
}]);
})();
Controller
Controller and service combined. Data registery controller function.
$scope.dataRegisteryForMyDirective = function (data, dataFlag) {
if (dataFlag) {
var svgDataMaster = data;
var svgData = {};
$scope.svgData = {};
svgData.layoutURI = data.layoutURI;
$scope.svgData = angular.copy(svgDataMaster);
$rootScope.reloadSvg = !$rootScope.reloadSvg;
}
else {
// making svg data empty, so we can execute the else block of directive --> init() and remove the cached data ###
$scope.svgData = {};
$rootScope.reloadSvg = !$rootScope.reloadSvg;
}
}
Service call and data registory call for directive:
$scope.loadSvgByNumber = function (inputNumber) {
var d = inputNumber;
myService.getData(d).then(function (resp) {
if (resp.status == "FAILURE") {
$scope.svgTempData = [];
$scope.dataRegisteryForMyDirective(resp, false);
// calling to dataRegisteryForMyDirective with false flag
}
else {
$scope.dataRegisteryForMyDirective(resp, true);
// calling to dataRegisteryForMyDirective with true flag
}
});
};

Related

Namespace With New Instance On Each Call

I'm trying to create something similar to d3(ex: d3.select()) but much more simple and I need to have a new instance each time I call the namespace function. Is this possible and/or am I approaching this wrong?
var dom = new function () {
var Element = null;
this.select = function (query) {
Element = document.querySelector(query);
return this;
};
this.append = function (elem) {
Element.append(elem);
return this;
};
};
Desired use
var bodyelement = dom.select("body");
var p = dom.select("p");
You need to run some code each time you use the dom object. So if the dom object was a function, you could call it to get a new instance.
var dom = function () {
var Element = null;
var newdom = {};
newdom.select = function (query) {
Element = document.querySelector(query);
return this;
};
newdom.append = function (elem) {
Element.append(elem);
return this;
};
return newdom;
};
console.log(dom() === dom(), "(false means the instances are different)");
var dom = new function () {
var Element = null;
this.select = function (query) {
Element = document.querySelector(query);
return this;
};
this.append = function (elem) {
Element.append(elem);
return this;
};
// add a way of accessing the resulting Element
this.element = function() { return Element; }
};
console.log(dom.select("body").element());
console.log(dom.select("p").element());
<p>blah</p>

Use Javascript Object to Angular Service

I am trying to add functions to a JS Object which will be used as a singleton service.
angular
.module('app.steps')
.factory('createStepsService', createStepsService);
createStepsService.$inject = [];
/* #ngInject */
function createStepsService() {
var steps;
var service = {
newSteps: function (current_step, total_steps) {
if (!steps) {
return new Steps(current_step, total_steps);
}
}
};
return service;
function Steps(current_step, total_steps) {
this.c_step = current_step;
this.t_step = total_steps;
}
Steps.prototype = {
addSteps: function (num) {
this.c_step += num;
},
setLastStep: function () {
this.lastStep = this.c_step = this.t_step;
}
};
}
When I run this line from the controller, I am not able to access
addSteps / setLastStep methods.
vm.createStepsService = createStepsService.newSteps(1, 3);
Why I don't see these methods? Were they created?
Thanks.
Your steps.prototype code is never ran.
This is because it appears after the return.
Change the order of your code to this:
/* #ngInject */
function createStepsService() {
var steps;
function Steps(current_step, total_steps) {
this.c_step = current_step;
this.t_step = total_steps;
}
Steps.prototype = {
addSteps: function (num) {
this.c_step += num;
},
setLastStep: function () {
this.lastStep = this.c_step = this.t_step;
}
};
var service = {
newSteps: function (current_step, total_steps) {
if (!steps) {
return new Steps(current_step, total_steps);
}
}
};
return service;
}
The reason that you can have a function declared before a return is because of JavaScript variable and function hoisting.
Your problem is that you are creating Steps.prototype after a return statement, so it will never be read.
In AngularJS, services are singletons objects that are instantiated only once per app.
And the factory() method is a quick way to create and configure a service.
It provides the function's return value i.e. Need to create an object, add properties to it, then it will return that same object.
For ex:
angular
.module('myApp',[])
.factory("createStepService", function(){
var stepServiceObj = {};
var c_step = 0;
var t_steps = 0;
var last_step = 0;
stepServiceObj.setCurrentStep = function(current_step){
c_step = current_step;
console.log('c_step1: ',c_step);
};
stepServiceObj.getCurrentStep = function(){
return c_step;
};
stepServiceObj.setTotalStep = function(total_steps){
t_steps = total_steps;
};
stepServiceObj.getTotalStep = function(){
return t_steps;
};
stepServiceObj.setLastStep = function(){
last_step = c_step = t_step;
};
stepServiceObj.getLastStep = function(){
return last_step;
};
stepServiceObj.addSteps = function(num){
return c_step += num;
};
return stepServiceObj;
});

A Javascript function which creates an object which calls the function itself

I am trying to make an angular service that returns a new object.
That's fine and good and works. new MakeRoll() creates an instance. But self.add, near the end also calls new MakeRoll() and that doesn't create an instance when I call add like I think it should.
I'm probably doing this all wrong but I haven't been able to figure it out.
var services = angular.module('services', []);
services.factory('Roll', [function() {
var MakeRoll = function () {
var self = {};
self.rolls = [];
self.add = function(number, sizeOfDice, add) {
var newRoll = {};
newRoll.number = number || 1;
newRoll.sizeOfDice = sizeOfDice || 6;
newRoll.add = add || 0;
newRoll.rollDice = function() {
var result = 0;
var results=[];
for (var i = 0; i < newRoll.number; i++) {
var roll = Math.floor(Math.random() * newRoll.sizeOfDice) + 1;
result += roll;
results.push(roll);
}
newRoll.results = results;
newRoll.result = result;
newRoll.Roll = new MakeRoll();
};
self.rolls.push(newRoll);
return self;
};
self.remove = function(index) {
self.rolls.splice(index, 1);
};
self.get = function(index) {
return self.rolls[index];
};
return self;
};
return new MakeRoll();
}
]);
angular service is designed to be singleton to accomplish some business logic, so don't mix up plain model with angular service. if you want to have more objects, just create a constructor and link it in service to be operated on.
function MakeRoll() {
...
}
angular.module('service', []).factory('Roll', function () {
var rolls = [];
return {
add: add,
remove: remove,
get: get
}
function add() {
// var o = new MakrRoll();
// rolls.push(o);
}
function remove(o) {
// remove o from rolls
}
function get(o) {
// get o from rolls
}
});

How to "new" a returned function in Javascript

I am trying to simulate a namespace feature in Javascript.
var com = {};
com.domain = {};
com.domain.system = {};
com.domain.net = {};
com.domain.net.ip = {};
com.domain.net.ip.tcp = {};
com.domain.net.ip.udp = {};
com.domain.net.ip.ssl = {};
com.domain.util = {};
com.domain.util.timer = {};
com.domain.plugins = {};
com.domain.session = {};
com.domain.io = {};
com.domain.algorithm = {};
com.domain.debug = {};
This is the namespaces declaration. Later I will add functions to these namespaces.
This is my selector function:
For a convenient way to use namespaces, I add a function named $. This function will walk all namespaces in com. If the selected name exists, return the object.
function $ (selector) {
function digger (namespace, selector) {
for (var prop in namespace) {
if (typeof namespace[prop] == "array" || typeof namespace[prop] == "object") {
if (prop == selector) {
return namespace[prop];
}
var dig = digger(namespace[prop], selector);
if (dig != null) {
return dig;
}
} else {
if (prop == selector) {
return namespace[prop];
}
}
}
}
return digger (com, selector);
}
After that, I add a timer to namespace com.doamin.util.
com.domain.util.timer = function () {
this._handle = new InnerObj.SystemTimer(io);
return this;
};
com.domain.util.timer.prototype.expiresFromNow = function (seconds, cbHandler) {
this._handle.ExpiresFromNow (seconds, cbHandler);
};
com.domain.util.timer.prototype.wait = function (seconds, cbHandler) {
this._handle.Wait (seconds, cbHandler);
};
com.domain.util.timer.prototype.expiresAt = function (seconds, cbHandler) {
this._handle.Wait (seconds, cbHandler);
};
com.domain.util.timer.prototype.cancel = function () {
this._handle.Cancel ();
};
Usage:
1. var timer = new com.domain.util.timer (); OK
timer.expiresAt (1, {}); OK
2. var func = $("timer"); OK
var timer = new func (); OK
timer.expiresAt (1, {}); OK
But but but but but
var timer = new $("timer") (); NG
Can anyone tell me why the last new function is not working?
Try var timer = new ($("timer"))();.
Your question is not clear but I guess since $("timer") returns a function, you want a new instance of the result of $("timer") and not a new instance of $().

Losing object reference on lookup in Javascript

I'm working on an extension for Google Chrome and I ran into the following situation:
I'm trying to get all the existing tabs from all the opened windows in the same instance of Google Chrome. I manage to get them and construct an array of objects that contain the relevant data for me.
When I look at the constructed array using console.log (which is saved for future use also) I can see the collection of objects, but I can't reference them (when I try I get undefined).
I tried to save the array outside my object in a container, but nothing changes.
Any idea why the reference to the objects go away when I try to look them up? Thanks.
Here is the code:
(function(window){
//defining a namespace
var example = {
bmarksmaster: (function() {
var bmarksmaster = function() {
return new bmarksmaster.fn.init();
}
bmarksmaster.fn = bmarksmaster.prototype = {
debug: false,
tabs: [],
constructor: bmarksmaster,
init: function() {
return this;
},
windowParser: function(ctx, filter) {
var local = ctx;
var filter = filter;
return function(wObj) {
if((wObj !== null) && (wObj !== undefined)) {
for(var idx in wObj) {
var cw = wObj[idx];
if((cw.tabs !== null) && (cw.tabs !== undefined)) {
var cwtabs = cw.tabs;
for(var tabIdx in cwtabs) {
local.tabs.push(filter(tabIdx, cwtabs[tabIdx]));
}
}
}
}
};
},
getTabs: function() {
var returnData = [];
chrome.windows.getAll(
{
"populate": true
}, this.windowParser(this, function(i, e) {
var data = {};
if(!e.incognito) {
data["title"] = e.title;
data['url'] = e.url;
data['favicon'] = e.favIconUrl || "";
}
return data;
}));
return this.tabs;
},
getTab: function(callback) {
this.getTabs();
for (var tabIdx in this.tabs) {
if(callback(tabIdx, this.tabs[tabIdx])) {
return this.tabs[tabIdx];
}
}
},
getTabsData: function(callback) {
var data = [];
var tabs = [];
tabs = this.getTabs();
console.log(this.tabs[0]);
for (var tabIdx in tabs) {
console.log(tabs[tabIdx]);
var tabData = callback(tabIdx, tabs[tabIdx]);
if(tabData) {
data.push(tabData);
}
}
return data;
},
setDebug: function() {
this.debug = true;
},
resetDebug: function() {
this.debug = false;
}
};
bmarksmaster.fn.init.prototype = bmarksmaster.fn;
return bmarksmaster;
})()
};
window.example = example;
})(window);
//end of bmarksmaster.js file
console.log(example.bmarksmaster().getTabs()); //this works, I can see the array
console.log(example.bmarksmaster().getTabs()[0]); //this doesn't work, I get undefined, never mind the shortcut
I think the logic in your code is wrong. It is a bit convoluted and hard to follow. I would recommend rewriting it a bit to be simpler. Something like this might help get you started. It collects all the windows, putts all the tabs into the tabs var.
var tabs = [];
chrome.windows.getAll({ populate: true}, function(windows) {
var localTabs = windows.reduce(function(a, b){
return a.tabs.concat(b.tabs);
});
tabs = localTabs.filter(function(element){
return !element.incognito;
});
})

Categories

Resources