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

AngularJS: The Performance Parts

Todd Motto
October 07, 2015

AngularJS: The Performance Parts

Todd Motto

October 07, 2015
Tweet

More Decks by Todd Motto

Other Decks in Programming

Transcript

  1. AngularJS
    the performance parts
    @toddmotto

    View full-size slide

  2. » Lead Engineer @ Mozio
    » Google Developer Expert
    » @toddmotto
    » Blog at toddmotto.com

    View full-size slide

  3. 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

    View full-size slide

  4. 1.2 to 1.3+ generic changes

    View full-size slide

  5. » IE8 support dropped
    » DOM manipulation
    » ~4.3 times faster
    » 73% less garbage
    » $digest loop
    » ~3.5 times faster
    » 78% less garbage
    » 400+ bug fixes

    View full-size slide

  6. 1.2 to 1.3+ perf features

    View full-size slide

  7. » One-time bind syntax
    » ngModelOptions
    » bindToController property
    » ngModel.$validators
    » ngMessage/ngMessages
    » strictDI
    » $applyAsync in $http
    » Disable debug info

    View full-size slide

  8. one time bindings
    {{ ::vm.name }}





    {{ ::user.name }}


    View full-size slide

  9. 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

    View full-size slide

  10. ng-Model-Options

    type="text"
    ng-model="vm.model"
    ng-model-options="{
    updateOn: 'default blur'
    }">

    View full-size slide

  11. ng-Model-Options

    type="text"
    ng-model="vm.model"
    ng-model-options="{
    updateOn: 'default blur',
    debounce: {
    'default': 250,
    'blur': 0
    }
    }">

    View full-size slide

  12. ng-Model-Options
    // directive controller
    function FooDirCtrl() {
    // undo model changes
    if (condition) {
    this.model.$rollbackViewValue();
    }
    }

    View full-size slide

  13. 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

    View full-size slide

  14. bindToController
    // directive controller
    function FooDirCtrl() {
    this.bar = {};
    this.doSomething = function doSomething(arg) {
    this.bar.foobar = arg;
    }.bind(this);
    }

    View full-size slide

  15. 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);
    }

    View full-size slide

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

    View full-size slide

  17. 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);
    }

    View full-size slide

  18. 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

    View full-size slide

  19. 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);

    View full-size slide

  20. 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);

    View full-size slide

  21. ngModel.$validators




    Not a valid VISA format!



    View full-size slide

  22. ngModel.$validators
    » ngModel.$validators Object
    » Instead of $parsers/$formatters
    » Return a Boolean from the bound function
    » Use with the $error Object in the View

    View full-size slide

  23. ngMessage/ngMessages


    Enter email:
    required ng-minlength="5" ng-maxlength="100">



    You did not enter a field


    Your email must be between 5 and 100 characters long



    View full-size slide

  24. ngMessage/ngMessages
    » Conditional validation
    » ngModel.$error Object
    » Acts like a switch case

    View full-size slide

  25. strictDI
    // implicit annotation
    function SomeService($scope, $timeout) {
    //...
    }
    angular
    .module('app')
    .factory('SomeService', SomeService);

    View full-size slide

  26. strictDI
    function SomeService($scope, $timeout) {
    //...
    }
    // Array annotations
    SomeService.$inject = ['$scope', '$timeout'];
    angular
    .module('app')
    .factory('SomeService', SomeService);

    View full-size slide

  27. strictDI
    » Runs the application's $injector in strict mode
    » Throws an error on Services using implicit
    annotations
    » Use ng-annotate to automate this process

    View full-size slide

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

    View full-size slide

  29. $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

    View full-size slide

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

    View full-size slide

  31. Disable debug info



    // content





    // content


    View full-size slide

  32. 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();

    View full-size slide

  33. Performance driven Angular

    View full-size slide

  34. Understand what impacts
    performance
    before you code

    View full-size slide

  35. Under the hood: $digest

    View full-size slide

  36. $digest fundamentals
    » $digest loop
    » $$watchers ($watch)
    » $$asyncQueue ($evalAsync)

    View full-size slide

  37. $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)

    View full-size slide

  38. $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

    View full-size slide

  39. $digest: $$asyncQueue
    » $evalAsync
    » Runs first in $digest
    » May run $digest again to flush $$asyncQueue

    View full-size slide

  40. track by
    Scenarios:
    * Large DOM lists
    * Slow DOM updates
    * $digests blocking UI thread (lagging)

    View full-size slide

  41. track by



    {{ user.name }}


    View full-size slide

  42. track by



    {{ user.name }}


    View full-size slide

  43. track by
    » Minimal DOM repaints (only what's changed)
    » Uses Object references instead of Angular hashes

    View full-size slide

  44. ng-if / switch vs ng-show / hide








    View full-size slide

  45. 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)

    View full-size slide

  46. ng-bind over {{ handlebars }}

    {{ vm.username }}




    Welcome to Facebook

    View full-size slide

  47. 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

    View full-size slide

  48. $apply or $digest?
    // forces a $rootScope.$digest();
    $scope.$apply();
    // forces a [current $scope].$digest();
    $scope.$digest();

    View full-size slide

  49. $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

    View full-size slide

  50. $destroy unbinding
    function myFunction () {
    // handle element clicks
    }
    // bind
    element.on('click', myFunction);
    // unbind
    $scope.$on('$destroy', function () {
    element.off('click', myFunction);
    });

    View full-size slide

  51. $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

    View full-size slide

  52. Deep $watch vs $watchCollection
    var prop = [{...},{...},{...},{...}];
    $scope.$watch('prop',
    function (newValue, oldValue) {
    }, true);
    $scope.$watchCollection('prop',
    function (newValue, oldValue) {
    });

    View full-size slide

  53. 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

    View full-size slide

  54. avoiding DOM filters

    {{ vm.date | date: 'dd-MM-yyyy' }}

    View full-size slide

  55. avoiding DOM filters
    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);

    View full-size slide

  56. avoiding DOM filters
    {{ vm.parsedDate }}

    View full-size slide

  57. avoiding DOM filters
    » DOM filters run twice per $digest
    » Preprocess in a Controller
    » Bind parsed value to the View

    View full-size slide

  58. 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

    View full-size slide

  59. Thank you
    @toddmotto
    speakerdeck.com/toddmotto

    View full-size slide