Karl Seamon a Senior Software Engineer at Google looks at performance problems encountered in large angular application and provides:
- Basics and Best Practices
- Diagnosing performance problems
- Improving performance within AngularJS
This video is from ng-conf 2014, Salt Lake City, Utah
Outline
The Problems: What can make an Angular app slow?
- Anything that could affect a normal JS app
- Slow DOM
- Single-threaded
- etc
- Inefficient directives *link vs compile *$parse vs $eval vs interpolation *Dirty checking *Lots of watchers *Slow watchers *Too many calls to $apply
What does slow mean?
- $apply > 25ms
- Click handler > 100ms
- Show a new page > 1s
- > 10s -> users will give up
- 200ms or less is ideal
Directives: compile, link, and constructor
- When a directive appears inside of a repeater
- compile is called only once
- 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)
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
- Simpler to use $apply all the time
- $apply has special error handling that $digest lacks
- So what’s $digest for?
- $apply = $rootScope.$digest + some other stuff
- 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 has two comparison modes
- referential (default) - quick, shallow comparison
- 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
$eval, $parse, and $interpolate
- $interpolate: returns function that evals “a string { {like} } this”
- $parse: returns function that evals “an.expression”
- $scope.$eval: evaluates “an.expression” (using $parse)
- 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
//unoptimized
for (var i = 0; i < 99999; i++) {
$scope.$eval(‘exp’);
}
//better
var parsedExp = $parse(‘exp’);
for (var i = 0; i < 99999; i++) {
parsedExp($scope);
}
Putting it together
//unoptimized
myApp.directive(function() {
return {
link: function(s, elem, attr) {
s.foo = scope.$eval(attr.foo);
scope.$watch(attr.list,
function(list) {
// do something
}, true);
}};});
//better
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
});
};}};});
$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.
//unoptimized
$scope.$watch(‘listOfBigObjects’,
myHandler, true);
//better
$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.
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.
//unoptimized
<div ng-repeat=”item in array”>
I live and die by { {item} }.
<div>
//better
<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
- Fewer bindings
- 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!
- Bind once at link and ignore digest cycles
- Bind at link and dirty check only on certain events
- 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
- Great for identifying which $watchers are causing problems
- Chrome profiler
- Offers a broader view, but harder to read
- performance.now()
- Provides microsecond resolution for measurements
comments powered by Disqus