KEMBAR78
Optimizing a large angular application (ng conf) | PPTX
Optimizing a Large
AngularJS Application
Karl Seamon
Senior Software Engineer, Google
Occasional contributor to AngularJS
Topics
● The Problems
● Basics and Best Practices
● Diagnosing performance problems
● Improving performance within AngularJS
The Problems: What can make an
Angular app slow?
● Anything that could affect a normal JS app
o Slow DOM
o Single-threaded
o etc
● Inefficient directives
o link vs compile
o $parse vs $eval vs interpolation
● Dirty checking
o Lots of watchers
o Slow watchers
o Too many calls to $apply
What does slow mean?
● $apply > 25ms
● Click handler > 100ms
● Show a new page > 1s
o > 10s -> users will give up
o 200ms or less is ideal
Directives: compile, link, and
constructor
● When a directive appears inside of a
repeater
o compile is called only once
o link and the constructor are called once per iteration
● When creating directives, try to get as much
work done as possible in the compile step
(or even earlier, in the factory)
● Example
o https://github.com/angular/angular.js/commit/f3a796
e522afdbd3b640d14426edb2fbfab463c5
Directives: Transclusion
● For directives that wrap other content
● Allows your directive to $digest its own
scope without causing dirty checks on
bindings in the user-provided contents
$digest and $apply
● Angular’s documentation favors $apply
o Simpler to use $apply all the time
o $apply has special error handling that $digest lacks
● So what’s $digest for?
o $apply = $rootScope.$digest + some other stuff
o If you update a child scope s, you can call
s.$digest to dirty-check only that s and its
descendants
Watchers
scope.$watch(valueExpression,
changeExpression, ...)
valueExpression will be executed many times -
Make sure it is fast! Avoid touching the dom
(_.debounce can help).
$watch and $watchCollection, con’t
● $watch has two comparison modes
o referential (default) - quick, shallow comparison
o deep - slow, recurses through objects for a deep
comparison; also makes a deep copy of the watched
value each time it changes
● Avoid deep $watch whenever possible
$watchCollection
● new in 1.2, used by ng-repeat
● Goes one level deep into the watched array
or object
● A nice alternative to deep $watch in many
cases
● $interpolate: returns function that evals “a
string {{like}} this”
● $parse: returns function that evals
“an.expression”
● $scope.$eval: evaluates “an.expression”
(using $parse)
$eval, $parse, and $interpolate
var parsedExp = $parse(‘exp’);
for (var i = 0; i < 99999; i++) {
parsedExp($scope);
}
$eval, $parse, and $interpolate, con’t
● Better to call $parse once and save the function than to call $eval many times
● $parse is much faster than $interpolate, prefer it when possible
for (var i = 0; i < 99999; i++) {
$scope.$eval(‘exp’);
}
Putting it together
myApp.directive(function($parse) {
return {
compile: function(elem, attr) {
var fooExp = $parse(attr.foo),
listExp = $parse(attr.list);
return function link(s, elem) {
s.foo = fooExp(s);
scope.$watchCollection(listExp,
function(list) {
// do something
});
};}};});
myApp.directive(function() {
return {
link: function(s, elem, attr) {
s.foo = scope.$eval(attr.foo);
scope.$watch(attr.list,
function(list) {
// do something
}, true);
}};});
$watch only what is needed
Sometimes a deep $watch is needed, but not
for the entire object.
By stripping out irrelevant data, we can make
the comparison much faster.
$scope.$watch(‘listOfBigObjects’,
myHandler, true);
$scope.$watch(function($scope) {
return $scope.listOfBigObjects.
map(function(bigObject) {
return bigObject.foo.
fieldICareAbout;
});
}, myHandler, true);
$watch before transforming, not
after
When applying expensive transformations to
input, watch the input itself for changes rather
than the output of the transformation.
Example:
https://github.com/angular/angular.js/commit/e2
068ad426075ac34c06c12e2fac5f594cc81969
ng-repeat - track by $index
By default, ng-repeat creates a dom node for
each item and destroys that dom node when
the item is removed.
With track by $index, it will reuse dom nodes.
<div ng-repeat=”item in array”>
I live and die by {{item}}.
<div>
<div ng-repeat=”item in array track by
$index”>
I live until {{array.length}} is
less than {{$index}}.
<div>
ng-if vs ng-show
● ng-show hides elements and bindings using
css
● ng-if goes a step further and does not even
create them
o Fewer bindings
o Fewer linkers called at startup
● ng-switch is like ng-if in this respect
Not really a Best Practice:
$$postDigest
● $$ means private to Angular, so be aware
that the interface is not stable
● Fires a callback after the current $digest
cycle completes
● Great for updating the dom once after dirty
checking is over
Avoiding dirty checking altogether
● Sometimes an expression’s output never
changes, but we dirty check it constantly
● Custom directives to the rescue!
o Bind once at link and ignore digest cycles
o Bind at link and dirty check only on certain events
o https://github.com/kseamon/fast-bind/tree/master/
 Implements bind
 class, href, if, switch, etc left as an exercise to
the reader
● Or maybe an optional module in 1.3?
Diagnosing performance problems:
Tools
● AngularJS Batarang
o Great for identifying which $watchers are causing
problems
● Chrome profiler
o Offers a broader view, but harder to read
● performance.now()
o Provides microsecond resolution for measurements
Using AngularJS Batarang
Looks like I should check
out canInlineEditItem.
Using the profiler
PS: Disable minification or you will be sad
Now use
performance.now()
t = 0; c = 0;
var myFunc = function() {
var d = performance.now();
c++;
// some code
// some more code
t += performance.now() - d;
};
Interpreting profiler output: My
function
Interpreting profiler output:
angular.copy & angular.equals
● If you see these in your profile, you probably have some deep $watches
that need to slim down (See Slide 7 and Slide 13)
● Tracking these down is bit tricky - we have to dive into angular.js
window.slowest = 0;
function copy(source, destination, recurse){
if (!recurse) var d = performance.now(), d2;
… copy(source, [], true); …
if (!recurse &&
(d2 = performance.now()) - d > slowest) {
slowest = d2 - d;
console.log(toJson(source), slowest);
console.trace();
}
return destination;
}
An incomplete list of recent
improvements to AngularJS
● 1.2.7 emoji-clairvoyance (2014-01-03)
o Scope: limit propagation of $broadcast to scopes that have listeners
for the event (80e7a455)
● 1.2.6 taco-salsafication (2013-12-19)
o compile: add class 'ng-scope' before cloning and other micro-
optimizations (f3a796e5)
o $parse: use a faster path when the number of path parts is low
(f4462319)
● 1.2.5 singularity-expansion (2013-12-13)
o $resource: use shallow copy instead of angular.copy (fcd2a813)
o jqLite: implement and use the empty method in place of html(‘’)
(3410f65e)
● 1.2.4 wormhole-blaster (2013-12-06)
o Scope: short-circuit after dirty-checking last dirty watcher (d070450c)
Future improvements AngularJS
● 1.3
o Coalesce asynchronous calls to $apply (#5297)
o Built-in bind-once
● Further future (maybe hopefully)
o $postDigestWatch - A public watcher based on $$postDigest for dom
updates (Also update ng-bind, ng-class, et al to use it) (#5828)
o Watcher deduplication (#5829)
o $apply isolation - Set regions (such as dialogs) for which $apply calls
do not affect the whole page (#5830)
o Your ideas - File a ticket! Write a pull request!
Thanks!

Optimizing a large angular application (ng conf)

  • 1.
    Optimizing a Large AngularJSApplication Karl Seamon Senior Software Engineer, Google Occasional contributor to AngularJS
  • 2.
    Topics ● The Problems ●Basics and Best Practices ● Diagnosing performance problems ● Improving performance within AngularJS
  • 3.
    The Problems: Whatcan make an Angular app slow? ● Anything that could affect a normal JS app o Slow DOM o Single-threaded o etc ● Inefficient directives o link vs compile o $parse vs $eval vs interpolation ● Dirty checking o Lots of watchers o Slow watchers o Too many calls to $apply
  • 4.
    What does slowmean? ● $apply > 25ms ● Click handler > 100ms ● Show a new page > 1s o > 10s -> users will give up o 200ms or less is ideal
  • 5.
    Directives: compile, link,and constructor ● When a directive appears inside of a repeater o compile is called only once o link and the constructor are called once per iteration ● When creating directives, try to get as much work done as possible in the compile step (or even earlier, in the factory) ● Example o https://github.com/angular/angular.js/commit/f3a796 e522afdbd3b640d14426edb2fbfab463c5
  • 6.
    Directives: Transclusion ● Fordirectives that wrap other content ● Allows your directive to $digest its own scope without causing dirty checks on bindings in the user-provided contents
  • 7.
    $digest and $apply ●Angular’s documentation favors $apply o Simpler to use $apply all the time o $apply has special error handling that $digest lacks ● So what’s $digest for? o $apply = $rootScope.$digest + some other stuff o If you update a child scope s, you can call s.$digest to dirty-check only that s and its descendants
  • 8.
    Watchers scope.$watch(valueExpression, changeExpression, ...) valueExpression willbe executed many times - Make sure it is fast! Avoid touching the dom (_.debounce can help).
  • 9.
    $watch and $watchCollection,con’t ● $watch has two comparison modes o referential (default) - quick, shallow comparison o deep - slow, recurses through objects for a deep comparison; also makes a deep copy of the watched value each time it changes ● Avoid deep $watch whenever possible
  • 10.
    $watchCollection ● new in1.2, used by ng-repeat ● Goes one level deep into the watched array or object ● A nice alternative to deep $watch in many cases
  • 11.
    ● $interpolate: returnsfunction that evals “a string {{like}} this” ● $parse: returns function that evals “an.expression” ● $scope.$eval: evaluates “an.expression” (using $parse) $eval, $parse, and $interpolate
  • 12.
    var parsedExp =$parse(‘exp’); for (var i = 0; i < 99999; i++) { parsedExp($scope); } $eval, $parse, and $interpolate, con’t ● Better to call $parse once and save the function than to call $eval many times ● $parse is much faster than $interpolate, prefer it when possible for (var i = 0; i < 99999; i++) { $scope.$eval(‘exp’); }
  • 13.
    Putting it together myApp.directive(function($parse){ return { compile: function(elem, attr) { var fooExp = $parse(attr.foo), listExp = $parse(attr.list); return function link(s, elem) { s.foo = fooExp(s); scope.$watchCollection(listExp, function(list) { // do something }); };}};}); myApp.directive(function() { return { link: function(s, elem, attr) { s.foo = scope.$eval(attr.foo); scope.$watch(attr.list, function(list) { // do something }, true); }};});
  • 14.
    $watch only whatis needed Sometimes a deep $watch is needed, but not for the entire object. By stripping out irrelevant data, we can make the comparison much faster. $scope.$watch(‘listOfBigObjects’, myHandler, true); $scope.$watch(function($scope) { return $scope.listOfBigObjects. map(function(bigObject) { return bigObject.foo. fieldICareAbout; }); }, myHandler, true);
  • 15.
    $watch before transforming,not after When applying expensive transformations to input, watch the input itself for changes rather than the output of the transformation. Example: https://github.com/angular/angular.js/commit/e2 068ad426075ac34c06c12e2fac5f594cc81969
  • 16.
    ng-repeat - trackby $index By default, ng-repeat creates a dom node for each item and destroys that dom node when the item is removed. With track by $index, it will reuse dom nodes. <div ng-repeat=”item in array”> I live and die by {{item}}. <div> <div ng-repeat=”item in array track by $index”> I live until {{array.length}} is less than {{$index}}. <div>
  • 17.
    ng-if vs ng-show ●ng-show hides elements and bindings using css ● ng-if goes a step further and does not even create them o Fewer bindings o Fewer linkers called at startup ● ng-switch is like ng-if in this respect
  • 18.
    Not really aBest Practice: $$postDigest ● $$ means private to Angular, so be aware that the interface is not stable ● Fires a callback after the current $digest cycle completes ● Great for updating the dom once after dirty checking is over
  • 19.
    Avoiding dirty checkingaltogether ● Sometimes an expression’s output never changes, but we dirty check it constantly ● Custom directives to the rescue! o Bind once at link and ignore digest cycles o Bind at link and dirty check only on certain events o https://github.com/kseamon/fast-bind/tree/master/  Implements bind  class, href, if, switch, etc left as an exercise to the reader ● Or maybe an optional module in 1.3?
  • 20.
    Diagnosing performance problems: Tools ●AngularJS Batarang o Great for identifying which $watchers are causing problems ● Chrome profiler o Offers a broader view, but harder to read ● performance.now() o Provides microsecond resolution for measurements
  • 21.
    Using AngularJS Batarang Lookslike I should check out canInlineEditItem.
  • 22.
    Using the profiler PS:Disable minification or you will be sad
  • 23.
    Now use performance.now() t =0; c = 0; var myFunc = function() { var d = performance.now(); c++; // some code // some more code t += performance.now() - d; }; Interpreting profiler output: My function
  • 24.
    Interpreting profiler output: angular.copy& angular.equals ● If you see these in your profile, you probably have some deep $watches that need to slim down (See Slide 7 and Slide 13) ● Tracking these down is bit tricky - we have to dive into angular.js window.slowest = 0; function copy(source, destination, recurse){ if (!recurse) var d = performance.now(), d2; … copy(source, [], true); … if (!recurse && (d2 = performance.now()) - d > slowest) { slowest = d2 - d; console.log(toJson(source), slowest); console.trace(); } return destination; }
  • 25.
    An incomplete listof recent improvements to AngularJS ● 1.2.7 emoji-clairvoyance (2014-01-03) o Scope: limit propagation of $broadcast to scopes that have listeners for the event (80e7a455) ● 1.2.6 taco-salsafication (2013-12-19) o compile: add class 'ng-scope' before cloning and other micro- optimizations (f3a796e5) o $parse: use a faster path when the number of path parts is low (f4462319) ● 1.2.5 singularity-expansion (2013-12-13) o $resource: use shallow copy instead of angular.copy (fcd2a813) o jqLite: implement and use the empty method in place of html(‘’) (3410f65e) ● 1.2.4 wormhole-blaster (2013-12-06) o Scope: short-circuit after dirty-checking last dirty watcher (d070450c)
  • 26.
    Future improvements AngularJS ●1.3 o Coalesce asynchronous calls to $apply (#5297) o Built-in bind-once ● Further future (maybe hopefully) o $postDigestWatch - A public watcher based on $$postDigest for dom updates (Also update ng-bind, ng-class, et al to use it) (#5828) o Watcher deduplication (#5829) o $apply isolation - Set regions (such as dialogs) for which $apply calls do not affect the whole page (#5830) o Your ideas - File a ticket! Write a pull request!
  • 27.

Editor's Notes

  • #2 Angular Working Group too, seemingly.
  • #5 For show a new page, it does not have to be fully loaded at that point, just ready enough to show some content. Showing feedback helps mitigate delays.
  • #10 Deep watch is enabled by passing true as the third argument to $watch.
  • #16 This advice is at odds with the previous slide - take it on a case by case basis.
  • #17 But it will still remove them if the array shrinks.
  • #18 The dom will still be compiled at startup, so if that is too much, you’ll need to employ a custom directive.
  • #20 We’ve developed a number of these directives in-house. We hoped to get them open-sourced in time for the conference, but it did not happen. So I made a toy implementation to show off the idea.
  • #23 The profiler is a good for getting an idea of where your problems lie. I prefer the top-down tree view with measured time rather than percentages. Just sort by total time and dig your way down.
  • #24 So the profiler shows you that one of your functions is slow. Now you can use performance.now() to get more info. You can move these lines around in your function to figure out which part(s) are the culprit.
  • #25 The output you see from this will vary depending on your data
  • #26 This list is not comprehensive, see the changelog for more