Blog

Care about memory – Development with Angular applications


The solution edoras one includes a document management system (DMS) that integrates into our work management toolkit. The technical challenges that this subsystem brings are complex and in the product team we work hard to improve the performance, reliability and usability of the components our clients use everyday. One of the key aspects to hande thousands of documents by hundreds of users each day is knowing and managing the resources the browser uses to display and edit documents.

JavaScript is a high level language that doesn’t include explicit memory management features. Memory gets automatically allocated when the program creates an object or released when the program no longer requires it. The JavaScript runtime relies on the garbage collector (GC) to decide when to release an object from memory. The GC looks for values that do not have any references to them, implying that they can no longer be accessed by the program.

Most of the time this collection of resources happens without any issues but there are situations when the GC cannot decide if an object should be released or not. Then the memory footprint of a program might grow unexpectedly and produce performance problems or crashes. It is in the hand of the developer to prevent and solve this issue.

edorasware illustration memory leak horizontal

When does Angular release a component from memory?

One common pattern in angular applications is to use ng-if to manage the visibility of parts of the application. When passed a “false” value, ng-if will remove its child element and will also call the $destroy event in its child scopes. The ng-view directive of the angular router will also replace the html elements of the transcluded contents when changing the route and also call $destroy on the transcluded scope.

The $destroy function nullifies the scope reference to the parent scope, severing the tree connection downwards. As that scope no longer has any references to it, it will be marked as collectible and released eventually by the GC.

How to create a memory leak

If we have a reference in a child scope to a higher node than the severed one or another object higher up in the tree then a reference will be maintained and the sub tree will be kept alive, creating a memory leak. There are many ways to create such references.

Culprit 1 — Event listeners

The most likely culprit of a memory leak in Angular is adding an event listener to a DOM object. If you forget to remove the listener on the $destroy event of your directive, it will hold a reference to a DOM node even if it is removed from the document. The DOM tree will then become a “Detached DOM tree” and will leak. Modern JS engines are able to catch most of these situations for you and remove the listeners, but more complex tree hierarchies can challenge even the best GC.

Culprit 2 — Subscriptions

If you use a library that exposes a subscription interface (Redux, RxJS, etc.) you have to remember to dispose the subscriptions on component removal.

Culprit 3 — Third parties

Sometimes a third party can hook itself to an element and prevent it from being collected even if you carefully design your listeners. Look for these leaks and try to avoid them too.

As you can see, if your code base grows large enough it becomes increasingly difficult to control these situations.

Strategies to prevent leaks

  • Nullify all references to models in a component on the component $destroy phase
  • Try to remove all circular dependencies by using another layer of indirection
  • Create a this.state object to manage the instance state in one place for easier nullification
  • Dispose subscriptions
  • Examine third party software and decide to use it or not also based on memory problems or contribute with pull requests

We should strive to remove all references to an object if it is not needed anymore. Not because it could not be collected by the GC if not doing so, but because if some bug arises in the framework or JS engine and it cannot be collected because of that, at least we can minimize the amount of memory that is leaked.

Nullify all — this.state pattern for components

As a quick win you can use this pattern to manage the state of your component and nullify them on destroy. This way you also explicitly define the state object holder.


export default class FooComponent {
   constructor() {
      this.state = {}
         $scope.$on('$destroy', () => {
           for (let member in this.state) this.state[member] = null
        })
     }
}

This will do a shallow nullification of the state object. If you have nested properties holding a reference up in the chain, you may still have a problem. In my opinion you should strive to keep your state object as shallow as possible. Mapping the state to the view should also be done as late as possible.
You can also create a recursive nullifying function for your state object or do it by hand by traversing the shape of your state.

Current status of memory leaks in AngularJS

The memory leak situation has improved a lot thanks to the effort of the Angular team and the community. Nevertheless it is a recurring problem. As this is a difficult topic to test, and occurs mostly in big applications, many issues are closed as not reproducible by the Angular team due to lack of context / feedback.

  • AngularJS Memory leak related issues — 10 open 140 closed
  • But not everything is on the framework, browsers also leak sometimes.

Conclusion

As profiling tools get better and JS engines do even more magic, maybe in the future we will actually see the promise of high level languages — not having to think about low level stuff like memory deallocation. For now, though, we have to be very careful when writing our applications and always keep in mind that as JavaScript developers we have to profile often. Look for the best performance, not only on first load, but also for long lived scenarios.

 

Roberto Lucha
Front-end Architect
roberto.lucha@edorasware.com

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Care about memory – Development with Angular applications


The solution edoras one includes a document management system (DMS) that integrates into our work management toolkit. The technical challenges that this subsystem brings are complex and in the product team we work hard to improve the performance, reliability and usability of the components our clients use everyday. One of the key aspects to hande thousands of documents by hundreds of users each day is knowing and managing the resources the browser uses to display and edit documents.

JavaScript is a high level language that doesn’t include explicit memory management features. Memory gets automatically allocated when the program creates an object or released when the program no longer requires it. The JavaScript runtime relies on the garbage collector (GC) to decide when to release an object from memory. The GC looks for values that do not have any references to them, implying that they can no longer be accessed by the program.

Most of the time this collection of resources happens without any issues but there are situations when the GC cannot decide if an object should be released or not. Then the memory footprint of a program might grow unexpectedly and produce performance problems or crashes. It is in the hand of the developer to prevent and solve this issue.

edorasware illustration memory leak horizontal

When does Angular release a component from memory?

One common pattern in angular applications is to use ng-if to manage the visibility of parts of the application. When passed a “false” value, ng-if will remove its child element and will also call the $destroy event in its child scopes. The ng-view directive of the angular router will also replace the html elements of the transcluded contents when changing the route and also call $destroy on the transcluded scope.

The $destroy function nullifies the scope reference to the parent scope, severing the tree connection downwards. As that scope no longer has any references to it, it will be marked as collectible and released eventually by the GC.

How to create a memory leak

If we have a reference in a child scope to a higher node than the severed one or another object higher up in the tree then a reference will be maintained and the sub tree will be kept alive, creating a memory leak. There are many ways to create such references.

Culprit 1 — Event listeners

The most likely culprit of a memory leak in Angular is adding an event listener to a DOM object. If you forget to remove the listener on the $destroy event of your directive, it will hold a reference to a DOM node even if it is removed from the document. The DOM tree will then become a “Detached DOM tree” and will leak. Modern JS engines are able to catch most of these situations for you and remove the listeners, but more complex tree hierarchies can challenge even the best GC.

Culprit 2 — Subscriptions

If you use a library that exposes a subscription interface (Redux, RxJS, etc.) you have to remember to dispose the subscriptions on component removal.

Culprit 3 — Third parties

Sometimes a third party can hook itself to an element and prevent it from being collected even if you carefully design your listeners. Look for these leaks and try to avoid them too.

As you can see, if your code base grows large enough it becomes increasingly difficult to control these situations.

Strategies to prevent leaks

  • Nullify all references to models in a component on the component $destroy phase
  • Try to remove all circular dependencies by using another layer of indirection
  • Create a this.state object to manage the instance state in one place for easier nullification
  • Dispose subscriptions
  • Examine third party software and decide to use it or not also based on memory problems or contribute with pull requests

We should strive to remove all references to an object if it is not needed anymore. Not because it could not be collected by the GC if not doing so, but because if some bug arises in the framework or JS engine and it cannot be collected because of that, at least we can minimize the amount of memory that is leaked.

Nullify all — this.state pattern for components

As a quick win you can use this pattern to manage the state of your component and nullify them on destroy. This way you also explicitly define the state object holder.


export default class FooComponent {
   constructor() {
      this.state = {}
         $scope.$on('$destroy', () => {
           for (let member in this.state) this.state[member] = null
        })
     }
}

This will do a shallow nullification of the state object. If you have nested properties holding a reference up in the chain, you may still have a problem. In my opinion you should strive to keep your state object as shallow as possible. Mapping the state to the view should also be done as late as possible.
You can also create a recursive nullifying function for your state object or do it by hand by traversing the shape of your state.

Current status of memory leaks in AngularJS

The memory leak situation has improved a lot thanks to the effort of the Angular team and the community. Nevertheless it is a recurring problem. As this is a difficult topic to test, and occurs mostly in big applications, many issues are closed as not reproducible by the Angular team due to lack of context / feedback.

  • AngularJS Memory leak related issues — 10 open 140 closed
  • But not everything is on the framework, browsers also leak sometimes.

Conclusion

As profiling tools get better and JS engines do even more magic, maybe in the future we will actually see the promise of high level languages — not having to think about low level stuff like memory deallocation. For now, though, we have to be very careful when writing our applications and always keep in mind that as JavaScript developers we have to profile often. Look for the best performance, not only on first load, but also for long lived scenarios.

 

Roberto Lucha
Front-end Architect
roberto.lucha@edorasware.com

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>