angular performance: then, now and the future. todd motto

71

Upload: future-insights

Post on 13-Jan-2017

9.031 views

Category:

Technology


1 download

TRANSCRIPT

AngularJSthe 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.2 to 1.3+

1.2 to 1.3+ generic changes

» 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.2 to 1.3+ perf features

» One-time bind syntax

» ngModelOptions

» bindToController property

» ngModel.$validators

» ngMessage/ngMessages

» strictDI

» $applyAsync in $http

» Disable debug info

one time bindings<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>

one time bindings» 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 controllerfunction 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 controllerfunction FooDirCtrl() { this.bar = {}; this.doSomething = function doSomething(arg) { this.bar.foobar = arg; }.bind(this);}

bindToController// directive controllerfunction FooDirCtrl($scope) { this.bar = {}; this.doSomething = function doSomething(arg) { this.bar.foobar = arg; // reference the isolate property $scope.name = arg.prop; }.bind(this);}

bindToControllerfunction fooDirective() { return { ... scope: {}, bindToController: { name: '=' }, ... };}

bindToController// directive controllerfunction 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 schoolfunction 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 schoolfunction 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 annotationfunction SomeService($scope, $timeout) { //...}

angular .module('app') .factory('SomeService', SomeService);

strictDIfunction SomeService($scope, $timeout) { //...}

// Array annotationsSomeService.$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

$applyAsync with $httpfunction 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

$applyAsync with $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

Disable debug infofunction config($compileProvider) { $compileProvider.debugInfoEnabled(false);}angular .module('app', []) .config(config);

Disable debug info<!-- 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>

Disable debug info» 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();

Performance driven Angular

Understand what impacts

performancebefore you code

Under the hood: $digest

$digest fundamentals» $digest loop

» $$watchers ($watch)

» $$asyncQueue ($evalAsync)

$digest: $digest loop» 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

track byScenarios: * Large DOM lists * Slow DOM updates * $digests blocking UI thread (lagging)

track by<!-- before --><ul> <li ng-repeat="user in vm.users"> {{ user.name }} </li></ul>

track by<!-- after --><ul> <li ng-repeat="user in vm.users track by user.id"> {{ user.name }} </li></ul>

track by» Minimal DOM repaints (only what's changed)

» Uses Object references instead of Angular hashes

ng-if / switch vs ng-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 / switch vs ng-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-bind over {{ 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-bind over {{ 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

$apply or $digest?// forces a $rootScope.$digest();$scope.$apply();

// forces a [current $scope].$digest();$scope.$digest();

$apply or $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

$destroy unbindingfunction myFunction () { // handle element clicks}

// bindelement.on('click', myFunction);// unbind$scope.$on('$destroy', function () { element.off('click', myFunction);});

$destroy unbinding» 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 $watch vs $watchCollectionvar prop = [{...},{...},{...},{...}];

$scope.$watch('prop', function (newValue, oldValue) {

}, true);

$scope.$watchCollection('prop', function (newValue, oldValue) {

});

Deep $watch vs $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

avoiding DOM filters<!-- vm.date = 1444175093303 --><p>{{ vm.date | date: 'dd-MM-yyyy' }}</p>

avoiding DOM filtersfunction 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);

avoiding DOM filters<p>{{ vm.parsedDate }}</p>

avoiding DOM filters» 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

Thank [email protected]/toddmotto