I am optimizing my large Angular App. As I found a that Google DevTools is very good to detect problems. As I just have started learning about DevTools, I am very confused about memory leaks.
When I move back and from to different pages in my app, Profile heap Snapshot size is increasing again and again so I think there are some object which is not being cleaned by GC and that's why my app is getting slow after sometime so how to solve this. Please help.
Note
This is what I understand using DevTools, please correct me if I am wrong. Other suggestions are welcome.
Till now what I have used
AngularOnce directive for reducing watch whenever required.
QuickList directive to replace ng-repeat with quick-ng-repeat.
InView Directive, to handle large list so I am removing DOM which is not in viewport.
Lazy load approach from ngInfiniteScroll directive.
Remove bindings to avoid memory leaks, Use Scopes
$destroy() Method.
Note:
The most likely culprit of memory leak in Angular is JQuery used in
your directives. If you attach an event-listener in your directive
using a JQuery plugin, the latter would keep a reference to your DOM
even after Angular deletes its own reference to the DOM, which means
it would never be garbage-collected by the browser, which in turn
means “Detached DOM tree” in your memory
In your Directive keep practice for unbinding the jQuery Event.
$destory Method which can be used to clean up DOM bindings before an
element is removed from the DOM.
$scope.$on("$destroy",function() {
$( window ).off( "resize.Viewport" );
});
Don't Forget To Cancel $timeout Timers In Your $destroy Events In
AngularJS
$scope.$on("$destroy",function( event ) {
$timeout.cancel( timer );
});
Related
I am coming to you at great despair - been working on this for two days now.
We have a Single Page Application built with Angular JS. We use the $routeProvider in HTML5 mode to achieve the SPA routing. Functionality wise - all works great!
We have one global controller attached to the body element, there is one controller in the header for a quick search functionality and all other controllers are scoped to a route. There is some data shared between controllers like a currentUser object and a ViewRes object that contains string values for user's chosen language.
But, we noticed that the Chrome service takes too much RAM for our page. I used Chrome Profiles tool to see what is happening. I disabled most of our code that was using a complex directive and left out only the basics. Memory consumption was lowered a lot, but it is still obviously there. Any time I change a page, the memory increases.
In the Heap Snapshot it shows that most memory is taken by (closure) and (array). Also the Detached DOM tree is big. Please note that these snapshots are with bare elements of our application (header, footer and lightweight content). If I include our complex UI components, then memory jumps from 14MB to 50MB to 140MB... and more. Obviously we will take care for these directives, but I am concerned that our issue is global, not just to bad design of the directives.
When I open the (array) element, I notice there are bunch of them with both shallow size and retained size of 6172. Tracking the scope of that object always leads to some ng directive, like ngShow, ngIf...
As you can see from the image, the tree ends at 'cache in function()'. We use Angular 1.3.6.
EDIT: This project also includes jQuery. We were using jQuery 1.8.2 and when I switched to 1.11.2, switching between simple pages (simple ng-repeats and simple models) doesn't cause memory leaks anymore (no more detached DOM elements).
Now the complex directives are still giving me too much detached elements, so I am going to work on those now and I'll post the results here when I find out the cause.
Difficult to say what your specific problems are, but common places for memory leaks in Angular include $interval, $watches and event handlers. Each of these functions creates a closure that is not cleaned up unless you explicitly delete it upon controller tear-down.
$interval in particular is nasty, as it will continue to run until you close the browser or the Web page - even if the user moves to a different tab or application, it won't stop running!
If you create references to DOM elements within these closures, you'll soon start chewing through memory, as the references are never released and the DOM tree becomes detached as the user moves from page to page.
To resolve this, ensure you handle the $destroy event in your controller (and your directives' controllers or link functions), and explicitly clean up after any intervals, watches or event handlers have been used.
You can do this by holding a reference to each $interval, watch or event handler and simply calling it as a function in the $destroy event handler.
For example:
// eventListener to remove
var eventListener = $scope.$on('eventName', function(){…});
// remove the eventListener when the $destroy event is fired
$scope.$on('$destroy', function(){
// call the value returned from $scope.$on as a function to remove
// the event listener
eventListener();
}
// remove an event listener defined on a DOM node:
var elementEventListener = element.on('eventName', function(){…});
element.on('$destroy', function(){
elementEventListener();
}
// Stop an interval
var stop = $interval(function(){...});
$scope.$on('$destroy', function(){
stop();
}
// Finally, unbind a $watch
var watchFn = $scope.$watch('someValue', function(newVal){…}
$scope.on('$destroy', function(){
watchFn();
}
Finally, NEVER store DOM elements in the scope! (see Point #2 here for the reason why).
I have a directive (), it renders a large number (>10000) of items in a table. It is used in one controller's view.
Normally, when I first visit this controller, it will take a long time to render the directive.
When I leave this controller and visit it again, it re-renders again. Is there a way to cache the rendered directive so that it can show instantly in the second visit?
just an idea, take the generated html and store it either in a service or in the local/session storage then you can check its validity using a timestamp or a data version and just reload that existing html into the container element, this would fix dependencies issues and would help you to build a more independent directive since you don't have to rely on the dom position and also this way several directive instances can render the same dataset if you use attributes for configuring your directive.
or you could render DOM in an asynchronous manner using a recursion function with a $timeout call something like
function Draw(data,index){
if(idx==data.length)
return;
//draw element idx
$timeout(function(){
Draw(data,index+1);
})
}
this way the directive will take longer but results will appear faster in the screen and you don't take the risk of blocking your browser
Is the performance hit the actual DOM rendering or some data processing that's done in the directive? If DOM manipulation is the issue, you could try hiding the content with an ng-show/ng-hide when you navigate away from the controller rather than unloading it. A good way to do this (if you're using ui-router) would be to put the directive in a parent view and have the content hidden or shown depending on which child view is active.
If it's data processing that's the issue, you can move the logic to a service. Services aren't unloaded when you change views, so it will be able to process the data once and supply it to the controller whenever is is loaded without additional processing.
I am trying to understand the lifetime of scopes in Angular and was wondering if doing $rootScope.$on or $rootScope.$watch ever goes out of scope (in the traditional sense)?
Does Angular have any type of Garbage Collection or anything like that?
Do you have to worry about releasing objects ($scopes)?
The entirety of angular scopes is a rather large piece of documentation, but you can start here or here.
The short, lofty answer is that angular inspects your modules (angular.module('myapp')) to create a complex tree of dependencies. Then the DOM is parsed to identify "ng" tags or other custom directives, which go through a compile phase (that constructs the custom HTML) and a linking phase (that generates a scope and attaches it to that element). That is about 1/100th of what really goes on, but it's a start.
Angular does perform garbage collection, but almost all of it happens behind the scenes. When a $scope is scheduled to be removed, one of the last things it does is fire a $scope.$destroy(), which you can hook into with $scope.$on('$destroy', function() { .. }) to perform any cleanup. Then again, in most instances you don't have to worry about cleanup or "releasing" anything, as angular takes care of that as well.
:Edit:
I also stumbled across this, which explains it best by far-- understanding scopes.
There is a heavy memory leak in my application but I haven't found out the causes, and here is the background.
I am using AngularJS + JQuery(plugins)
Many listeners are bound like the following:
$(element).on("keyup", function() {});
So the question is
Do I need to unbind those listeners in directives by following?
scope.$on("$destroy", function() {
$(element).off();
});
BTW, how do you usually find out the memory leak in a web application?
I use chrome's profile (see here Profiling memory performance)
but I could not trace to the codes where memory leaks. Do you have any suggestions?
Thanks a lot!
The Angular documentation for scope destroy, implies that you do need remove DOM events.
http://docs.angularjs.org/api/ng.$rootScope.Scope#$destroy
Note that, in AngularJS, there is also a $destroy jQuery event, which
can be used to clean up DOM bindings before an element is removed from
the DOM.
I'm using the router (was using the built in one, now using ui-route but solutions for either are fine) in Angular.JS to switch between control/template pairs. When switching back and forth between a couple of these pages it takes up to a second to setup the DOM each time which looks terrible. Is there anyway of having angular keep around the DOM tree instead of recreating it each time. I guess I'd like to just hide/sow the bits for each page rather than remove/re-create them each time.
Any suggestions welcome!
You have to write your own ng-view directive to create such functionality.
The basic idea behind it is:
Before the route changes, instead of destroying the current view element, and scope, you just put it in an invisible cache DIV, and deregister scope listeners. Give the element a data attribute with the $$route.templateUrl to be able to get it back.
Then you fetch the next view from the server.
Before the route changes you check for cache item existance, and if it is in your cache div get the element from the cache, re-register listeners and put the current view the cache.
The tricky part is not to mess the $scopes up. So you might need a constructor and destructor in your $scope for the event, and maybe for the $watchers as well. I'm not sure.
But to be honest, if you using the template cache and still takes 1 second or so to render, then you might have some inefficient $watch expression, or a huge ng-repeat. You should consider some refact.
I've been researching this myself. I am working on rather old hardware in an unaccelerated browser. Initial render is a problem. My early work-around was to cache pre-compiled templates, as you are attempting. I found that this provided only a minimal speed improvement.
The real bottleneck was coming from my ng-repeat directives, the number of reflows that resulted and the number of DOM nodes/watchers I was building in each iteration.
Some sources have suggested creating a custom directive which manually assembles the dom and appends it in one go. The result is very minimal reflow and no watchers. The downside is very large. No more angular fun and a lot of unnecessary work. More importantly, none of this provided a large enough speed improvement to justify the work.
I ultimately found that the best speed improvement came from forcing hardware acceleration for each ng-repeat iteration. As suggested above, the ng-animate directive in newer versions angular make this relatively trivial.
You will see immediate page render, with minor reflow hiccups. ng-cloak does not help here. Due to the animation request, the page is not suposed to be cloaked while the repeat renders. These can, however, be rendered reasonably well with a bit of clever fun. I'm actually hiding the ng-repeat until the $location changes, showing a progress indicator in the meantime, and then toggling my ng-show. This works really nicely.
Having said all of that, precompiling your templates should be done as follows.
1) When your app starts, create a new cache for yourself. See http://docs-angularjs-org-dev.appspot.com/api/ng.$cacheFactory
2) Populate this cache with compiled templates. Inject $compile and call it on each template. Compile returns a function which you will later call against your scope. Key this function in your cache as you see fit.
3) Create a custom directive which accepts a cache key as an attribute. Inside this directive, query your compile cache for the correct compile function. Call the function against your current scope, and append the resulting DOM to the element passed into the directive.
4) Sorta win :).
If you move up to Angular 1.1.5, you can use the ng-animate attribute on you ng-view tag.
I'm not 100% sure, but I think it does some DOM caching to make the transitions work better. You could try adding adding ng-animate attribute to your tag. That might take care of it for you.