Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Angular 1.x Performance Pitfalls

Angular 1.x Performance Pitfalls

Connect.Tech 2016
Performance and memory can quickly get away from you in JavaScript, and some features in Angular 1 can exacerbate the problem. We will discuss some basic causes for performance drains and memory leaks in JavaScript and some specific misuses of Angular that run into them.

Avatar for K. Devin McIntyre

K. Devin McIntyre

October 21, 2016
Tweet

More Decks by K. Devin McIntyre

Other Decks in Programming

Transcript

  1. Mistake #1: I took a DOM element out of the

    DOM, so now it’s gone • Elements removed from DOM still exist in memory as long as something references them • Anything referenced by any existing DOM object (e.g. inside event listeners) still exists in memory • Problem with Angular 1: lots of pointers and listeners
  2. Correcting DOM references 1. Avoid closures – Example 1: Use

    bind on listener functions 2. Always remove listeners 3. Always remove pointers
  3. Remove all listeners from HTML: in jQuery • Manually destroy

    scopes first to trigger destroy listeners • Example of remove() (removes element and all children): 1. elem.children().addBack().scope().$destroy(); 2. elem.remove(); • Example of empty() (removes children only): 1. elem.children().scope().$destroy(); 2. elem.empty(); • element.detach() does not remove listeners
  4. Remove all listeners from HTML: in Angular • Preferred: Use

    jQuery remove() or empty() on $destroy • If the element can’t be removed, use unbind, off, etc.
  5. Mistake #2: Closures will take care of themselves • Especially

    important for promises, $timeout, $watch, $interval, etc. 1. Pass in parameters instead 2. Make it an object method 3. Use bind to control context and/or pre- define parameters
  6. Controlling Closures • Set function to undefined or null during

    a destruction sequence – e.g. $scope.on(‘$destroy’) • All $timeouts and $intervals should be canceled during destruction • Example 2: $safeDestruct
  7. Avoiding circular references 1. Keep parent-child relationships in mind 2.

    Avoid passing this in using a closure – var that = this – var self = this – var _this = this
  8. Angular scope circular references • Do not use $scope inside

    a function on $scope. • Really, don’t.
  9. Mistake #4: $scope is a good place to put things

    • Potential to retain memory is large • Reference problems I described before can trap a scope in memory, and all its pointers with it • If you created the scope with $new, you also have to destroy it with $destroy
  10. Minimizing scope contents 1. Use singleton objects, such as angular

    services, for the majority of functions and state objects 2. Use accessor properties 3. Example 3: Object.defineProperty
  11. Minimize scope contents: $watches • Watches involving local copies: do

    not use large objects – Equality watch (third parameter true) – String references ($watch(‘nameOfObj’) or ng- model etc.) • For watching a large object, watch a quick function instead – Example 4 • On $destroy event, trigger the destruction of all existing watches
  12. How to Take a Heap Snapshot 1. Run without debug

    mode – Debuggers can add pointers to objects 2. Clear console – More pointers 3. Force garbage collection
  13. How to Take a Heap Snapshot 4. Select “Take heap

    snapshot” in Profiles tab – On later snapshots, use button in corner
  14. Detecting Memory Leaks Take snapshot after some object(s) should have

    been garbage collected • Snapshot 1 = on load of app • 2 & 3 = open and close of new print job in same window • Snapshot 2 includes some new compiled code, so the memory difference is less than it seems • Leak of new print job without any editing: ~3.7MB
  15. Detecting Memory Leaks Compare heap contents between jobs • Choose

    “Comparison” option to view differences, “Summary” to view all memory • Sort by Size Delta to see most memory, Constructor to see object types
  16. Tip: Use Typed Objects • We easily detect a leak

    because of the function identifier being used • function TypeA(param) {} • var TypeB = function TypeB(param) {} • NOT: • var TypeC = function() {}
  17. Largest Retained Object Types • (array) = JS internal (e.g.

    object properties) • (compiled code) and (system) = JS internal • (closure) = all closures (not pointers) – Most leaked closure memory is in HTML elements • (string) and (concatenated string) = all strings – Files using ng-include are strings • system / Context = insides of functions • HTML object types – NodeList, NamedNodeMap, Text, Attr, Comment – HTMLCollection, HTMLSpanElement, etc. • Object = any untyped object – Some are from our code, some from libraries • Array = all Arrays from our code and libraries • a.$$ChildScope.$$ChildScope = angular scope type – n.fn.init,Xb, and h are properties in scopes
  18. Largest Retained Object Types • More job loads indicate what

    is growing the most: Object, Array, (closure), and Angular (1.3.0) scopes
  19. Retainers • Click an object to see its retainers below

    • Objects in blue are inside closures
  20. Example memory leak • Scope leaking via a watch that

    was not removed (from ng- repeat of font list in Angular 1.3.0) • This issue was fixed by Angular 1.3.5
  21. Investigating scope leaks • Hover over a scope to see

    its properties • Properties can tell us what scope this is, so you can figure out if it’s still supposed to be around
  22. Investigating scope leaks • Quick check: use typed objects to

    find whether a scope is leaking – You can make a temporary object type just for this
  23. Angular performance drains: what “everybody” knows • Watchers, especially ones

    that run non-trivial functions • Ng-repeat: may not garbage collect correctly – Write your own repeater that does not use watching to monitor its collection – Use var val in ::bigCollection on collections that won’t change • Repeated filters – Use ::something if it is not going to change • The default settings of ng-model – Debounce: <input ng-model="user.name" ng-model- options="{ debounce: 1000 }" />
  24. Angular performance drains: what “everybody” knows • Not always correct:

    “Use ng-if instead of ng- show” – If it’s constantly being toggled on and off – you’re going to reuse it frequently – you probably want ng-show – If it has a ton of listeners that slow things way down when it’s hidden with ng-show, you probably want ng-if – If you have both, refactor
  25. Performance improvements • Stop triggering unnecessary digests – Use $scope.evalAsync()

    instead of $scope.$apply if possible, because it does not trigger another digest – Use $scope.$digest for applying local changes and $scope.$apply for applying app-wide changes – Use the third parameter of $timeout whenever you possibly can to avoid an extra $scope.$apply • $timeout(fn.bind(context), delay, false)
  26. Performance improvements • Add and remove $watches as necessary –

    The overhead of removing and replacing a $watch on a hidden or unused element is often much less than leaving that $watch in place – Whenever you set a $watch, it returns its own unbinding function, which you can use to turn off the $watch again; you can reset it when you know it’s necessary – If you want to toggle watches set within the HTML, try these (I have not tried them yet): • bindonce: github.com/Pasvaz/bindonce • $watch fighters: github.com/abourget/abourget-angular • github.com/scalyr/angular • Swapping out your $$watchers array with a blank one (risky)
  27. Performance improvements • Avoid using ng-mouseover because $scope.$apply gets called

    every time the mouse moves at all – Use ng-mouseenter + ng-mouseleave when possible – Or, bind directly on the element (be sure to unbind the listener again within $scope.on(‘$destroy’)), and use $scope.$digest at the end of the listener function if anything changed that needs to be listened to