AngularJS
the performance parts@toddmotto
» Lead Engineer @ Mozio
» Google Developer Expert
» @toddmotto
» Blog at toddmotto.com
Topics
» New features (1.2 - 1.3+)
» Generic changes
» Perf features
» Performance driven Angular
» $digest loop/$$watchers/$$asyncQueue
» Quick wins, tips and tricks
» Structure practices, advanced techniques
1.2to1.3+
1.2to1.3+genericchanges
» IE8 support dropped
» DOM manipulation
» ~4.3 times faster
» 73% less garbage
» $digest loop
» ~3.5 times faster
» 78% less garbage
» 400+ bug fixes
1.2to1.3+perffeatures
» One-time bind syntax
» ngModelOptions
» bindToController property
» ngModel.$validators
» ngMessage/ngMessages
» strictDI
» $applyAsync in $http
» Disable debug info
onetimebindings
<p>{{ ::vm.name }}</p>
<p ng-bind="::vm.name"></p>
<div ng-if="::vm.user.loggedIn"></div>
<div ng-class="::{ loggedIn: vm.user.loggedIn }"></div>
<ul>
<li ng-repeat="user in ::vm.users">
{{ ::user.name }}
</li>
</ul>
onetimebindings
» Defined with ::
» $watched until not "undefined"
» $$watcher is unbound
» Will not update upon Model changes
» One-time, not one-way
» Great for single static rendering
ng-Model-Options
<!-- updateOn -->
<input
type="text"
ng-model="vm.model"
ng-model-options="{
updateOn: 'default blur'
}">
ng-Model-Options
<!--
debounce:
- example will debounce 250ms when typing
- example will update model immediately on "blur"
-->
<input
type="text"
ng-model="vm.model"
ng-model-options="{
updateOn: 'default blur',
debounce: {
'default': 250,
'blur': 0
}
}">
ng-Model-Options
// directive controller
function FooDirCtrl() {
// undo model changes
if (condition) {
this.model.$rollbackViewValue();
}
}
ng-Model-Options
» Fine tune how Model updates are done
» Define event types
» Add debounce to delay Model synchronisation
» e.g. { debounce: 250 } = $digest ~250ms
» $rollbackViewValue for undoing model changes
bindToController
// directive controller
function FooDirCtrl() {
this.bar = {};
this.doSomething = function doSomething(arg) {
this.bar.foobar = arg;
}.bind(this);
}
bindToController
// directive controller
function FooDirCtrl($scope) {
this.bar = {};
this.doSomething = function doSomething(arg) {
this.bar.foobar = arg;
// reference the isolate property
$scope.name = arg.prop;
}.bind(this);
}
bindToController
function fooDirective() {
return {
...
scope: {},
bindToController: {
name: '='
},
...
};
}
bindToController
// directive controller
function FooDirCtrl() {
this.bar = {};
this.doSomething = function doSomething(arg) {
this.bar.foobar = arg;
// reference the isolate property
this.name = arg.prop;
}.bind(this);
}
bindToController
» Used with "controllerAs" (class-like)
» Binds isolate props to the Controller instance
» No $scope
» $scope remains "special use only"
» Not used for data
» Used for $watch/$on/etc
ngModel.$validators
// old school
function visaValidator() {
var VISA_REGEXP = /^4[0-9]{12}(?:[0-9]{3})?$/;
function link($scope, element, attrs, ngModel) {
ngModel.$parsers.unshift(function (value) {
var valid = VISA_REGEXP.test(value);
ngModel.$setValidity('visaValidator', valid);
return valid ? value : undefined;
});
}
return { require : 'ngModel', link : link };
}
angular.module('app').directive('visaValidator', visaValidator);
ngModel.$validators
// new school
function visaValidator() {
var VISA_REGEXP = /^4[0-9]{12}(?:[0-9]{3})?$/;
function link($scope, element, attrs, ngModel) {
ngModel.$validators.visaValidator = function (value) {
return VISA_REGEXP.test(value); // Boolean
};
}
return { require : 'ngModel', link : link };
}
angular.module('app').directive('visaValidator', visaValidator);
ngModel.$validators
<form name="myForm">
<label>
<input type="text" ng-model="myForm.card" visa-validator>
<div ng-if="myForm.myPassword.$error.visaValidator">
Not a valid VISA format!
</div>
</label>
</form>
ngModel.$validators
» ngModel.$validators Object
» Instead of $parsers/$formatters
» Return a Boolean from the bound function
» Use with the $error Object in the View
ngMessage/ngMessages
<form name="myForm">
<label>
Enter email:
<input type="text" ng-model="field" name="myField"
required ng-minlength="5" ng-maxlength="100">
</label>
<div ng-messages="myForm.myField.$error" role="alert">
<div ng-message="required">
You did not enter a field
</div>
<div ng-message="minlength, maxlength">
Your email must be between 5 and 100 characters long
</div>
</div>
</form>
ngMessage/ngMessages
» Conditional validation
» ngModel.$error Object
» Acts like a switch case
strictDI
<div ng-app="myApp" ng-strict-di>
<!-- app -->
</div>
strictDI
// implicit annotation
function SomeService($scope, $timeout) {
//...
}
angular
.module('app')
.factory('SomeService', SomeService);
strictDI
function SomeService($scope, $timeout) {
//...
}
// Array annotations
SomeService.$inject = ['$scope', '$timeout'];
angular
.module('app')
.factory('SomeService', SomeService);
strictDI
» Runs the application's $injector in strict mode
» Throws an error on Services using implicit
annotations
» Use ng-annotate to automate this process
$applyAsyncwith$http
function config($httpProvider) {
$httpProvider.useApplyAsync(true);
}
angular
.module('app', [])
.config(config);
More:
blog.thoughtram.io/angularjs/2015/01/14/exploring-angular-1.3-speed-up-with-applyAsync.html
$applyAsyncwith$http
» Enables $applyAsync to be used with $http
» Schedules an async $apply for batched requests
» For requests that resolve within ~10ms
» Pushes into $$asyncQueue
» Single $digest
Disabledebuginfo
function config($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}
angular
.module('app', [])
.config(config);
Disabledebuginfo
<!-- enabled -->
<div ng-controller="MainCtrl as vm" class="ng-scope ng-binding">
<my-directive class="ng-isolate-scope">
// content
</my-directive>
</div>
<!-- disabled -->
<div ng-controller="MainCtrl as vm">
<my-directive>
// content
</my-directive>
</div>
Disabledebuginfo
» Disable in production for performance boosts
» Removes $scope references on elements
» Doesn't add classes to DOM nodes with binding info
» Enable in console with
angular.reloadWithDebugInfo();
PerformancedrivenAngular
Understandwhatimpacts
performance
beforeyou code
Underthe hood: $digest
$digestfundamentals
» $digest loop
» $$watchers ($watch)
» $$asyncQueue ($evalAsync)
$digest: $digestloop
» Triggered by $scope.$apply / built-in events
» Iterates $$watchers Array on $scope
» If model value is different from last calculated
then corresponding listener executes
» Exits loop, Angular loops again (10 max)
» Repaints DOM (View expressions updated)
$digest: $$watchers
» View events/bindings {{ foo }}
» Angular adds a watch to the $watch list
» Only $watched if bound in the View
» Dirty checked in the $digest loop
» Minimise use of $$watchers / avoid if possible
$digest: $$asyncQueue
» $evalAsync
» Runs first in $digest
» May run $digest again to flush $$asyncQueue
trackby
Scenarios:
* Large DOM lists
* Slow DOM updates
* $digests blocking UI thread (lagging)
trackby
<!-- before -->
<ul>
<li ng-repeat="user in vm.users">
{{ user.name }}
</li>
</ul>
trackby
<!-- after -->
<ul>
<li ng-repeat="user in vm.users track by user.id">
{{ user.name }}
</li>
</ul>
trackby
» Minimal DOM repaints (only what's changed)
» Uses Object references instead of Angular hashes
ng-if/switchvsng-show/hide
<!-- ng-show -->
<ul ng-show="vm.exposeNav">
<li ng-repeat="menu in vm.menus"></li>
</ul>
<!-- ng-if -->
<ul ng-if="vm.exposeNav">
<li ng-repeat="menu in vm.menus"></li>
</ul>
ng-if/switchvsng-show/hide
» ng-if/switch reconstruct the DOM
» ng-if/switch for less frequent/heavier rendering
» ng-show/hide toggle "ng-hide" class
» ng-show/hide for more frequent/lighter rendering
» ng-show/hide less performant due to $$watchers
(when hidden)
ng-bindover{{handlebars}}
<!-- handlebars -->
<p>{{ vm.username }}</p>
<!-- ng-bind -->
<p ng-bind="vm.username"></p>
<!-- perf example -->
<p>
Welcome <span ng-bind="vm.username"></span> to Facebook
</p>
ng-bindover{{handlebars}}
» No DOM flicker (invisible bindings) with ng-bind
» Significantly faster
» ng-perf.com/2014/10/30/tip-4-ng-bind-is-faster-
than-expression-bind-and-one-time-bind
» Lesser need for ng-cloak
» Angular won't evaluate entire text content
$applyor$digest?
// forces a $rootScope.$digest();
$scope.$apply();
// forces a [current $scope].$digest();
$scope.$digest();
$applyor$digest?
» $scope certainties
» Prevent a full $rootScope.$digest() if you're
certain only child $scopes need updating
» Improve performance by not forcing a full
$rootScope.$digest
» $scope.$digest runs on current and child $scopes
» $scope.$apply triggers $rootScope.$digest call
$destroyunbinding
function myFunction () {
// handle element clicks
}
// bind
element.on('click', myFunction);
// unbind
$scope.$on('$destroy', function () {
element.off('click', myFunction);
});
$destroyunbinding
» Remove event listeners that may cause memory leaks
» DOM nodes that are destroyed
» Manually unbind by listening to $destroy
» $scope.$on events are automatically removed
Deep$watchvs$watchCollection
var prop = [{...},{...},{...},{...}];
$scope.$watch('prop',
function (newValue, oldValue) {
}, true);
$scope.$watchCollection('prop',
function (newValue, oldValue) {
});
Deep$watchvs$watchCollection
» Deep $watch uses deep Object tree comparison
(expensive)
» $watchCollection goes only one level deep
» Shallow reference of all top level items
» Try not to use either unless you have to
» Not as testable
» Signs of bad architecture
» Litter Controllers
avoidingDOMfilters
<!-- vm.date = 1444175093303 -->
<p>{{ vm.date | date: 'dd-MM-yyyy' }}</p>
avoidingDOMfilters
function SomeCtrl($filter) {
// date passed in elsewhere
var time = 1444175093303;
// Parsed in JS before bound to the DOM
this.parsedDate = $filter('date')(time, 'dd-MM-yyyy');
}
angular
.module('app')
.controller('SomeCtrl', SomeCtrl);
avoidingDOMfilters
<p>{{ vm.parsedDate }}</p>
avoidingDOMfilters
» DOM filters run twice per $digest
» Preprocess in a Controller
» Bind parsed value to the View
Takeaways
» Understand the $digest loop
» Investigate the performance side of each
Directive/API you use
» Consider using JavaScript over DOM bindings where
possible ($filter etc.)
» Check Angular's GitHub repo for changelogs/
releases
Thankyou
@toddmotto
speakerdeck.com/toddmotto
Angular Performance: Then, Now and the Future. Todd Motto

Angular Performance: Then, Now and the Future. Todd Motto