Angular JS – Performance – goo.gl/9ylsTB



Angular JS – Performance – goo.gl/9ylsTB

0 0


angularPerf


On Github khan4019 / angularPerf

Angular JS

Performance

goo.gl/9ylsTB

www.thatJSDude.com / @mdkhan005

(youtube.com/user/khanLearning)

Angular app

Build one

  • Create input
  • show two way bindings
  • using ng-model and expression
angular js pitfalls

Magic

Two phases

  • Compilation phase
  • Runtime phase

Compilation Phase

<input ng-model="name">
						
input with ng-model becomes angular directive Setup a keydown listener to input
<h1>{{name}}</h1>
						
Something in angular named: $interpolation interpolation: markup with expression => string interpolation setup a $watch in current scope $watch will be notified if "name" changes $compile vs $interpolate vs $parse

Runtime Phase

When you press 'X' Browser emits a keydown event on input input directive captures change calls $apply("name = 'X';") to update model Angular applies the name = 'X'; to the model The $digest loop begins $watch list detects change notifies $interpolation $interpolation updates the DOM by $parse angular finishes keydown event browser render new text scope performance considerations

Scope

Scopes

  • JavaScript Object
  • rootscope is created during app bootstrap
  • Prototypical Inheritance
  • Arranged in hierarchical structure
  • Mimic the DOM structure of the application
  • Stores an array of functions $$watchers
  • Can watch expressions and propagate events.
  • new scope creates more values for GC to collect later
scope and understanding scope

What can be done

  • small scope will be faster than larger
  • $destroy()- Removes the current scope (and all of its children)
  • $destroy - makes scope eligible for GC
Large angular app performance

ng-show

how it works

<div ng-show="myValue"></div>
						
  • show or hide html element
  • based on an expression
  • by adding and removing .ng-hide class
  • will create and evaluate hidden elements
  • display none
start from here

ng-if

<span ng-if="checked">
  This is removed when the checkbox is unchecked.
</span>
						
  • completely removes and recreate DOM
  • elements which is not in ur DOM, has less impact
  • ng-if creates a child scope while ng-show/ng-hide does not
  • ng-switch does the same thing

What should i use?

  • How frequently will this change?
  • the more frequent, the worse fit ng-if is
  • How heavy is the scope?
  • the heavyer, the better fit ng-if is

filter

how often filter executes?

{{ filter_expression | filter : expression : comparator}}
						
  • Filters are really simple to use
  • We insert a pipe, the filter name and we’re done
  • Every single filter twice per $digest cycle
  • First run is from the $$watchers detecting any changes
  • Second run is to see if there are further changes that need updated values

show filter demo

limit DOM filter

What should I do

  • Filter data early instead of template filters when possible
{{ filter_expression | filter : expression : comparator }}
						
$filter('filter')(array, expression, comparator);
						
how often filter executes? Filters are really simple to use, we insert a pipe, the filter name and we’re done. However, Angular runs every single filter twice per $digest cycle once something has changed. This is some pretty heavy lifting. The first run is from the $$watchers detecting any changes, the second run is to see if there are further changes that need updated values. Here’s an example of a DOM filter, these are the slowest type of filter, preprocessing our data would be much faster. If you can, avoid the inline filter syntax.

ng repeat

how ng-repat works

Iterates over each item Runs via filters, if any Calculates a hash value to identify the object has already created a DOM If so, it reuses it If not, creates DOM based on ng-repeat template Insert into the actual DOM A watch is added on the array watch triggers step 1 again if the array undergoes any change two ng repeat improvement suggestion

ng-include in ng-repeat

  • Each time -
  • clone ngInlcude directive to run a link function
  • injects content
  • calls $compile
  • html in the ng-include needs to be parse again and again
ng repeat with nginclude

browser will spend time to parse html for each item

What can i do?

<li ng-repeat="item in filteredItems()"> 
						
 
<li ng-repeat="item in items">
						

what can i do?

  • Pagination
  • limitTo
<table>
    <tr ng-repeat="d in data | limitTo:totalDisplayed">
        <td>{{d}}</td>
   </tr>
</table>
						
debounce user input (searching) tuning long list

ng Infinite Scroll

  • don't load everything use ng infinite scroll
read about ngInfiniteScroll

track by

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 item.Id”>
  I live until {{array.length}} is
  less than {{$index}}.
<div>
						
ng-repeat performance by track by

bind once

git: bindonce

summary ng repeat

  • Do not evaluate function
  • reduce filter
  • pagination
  • limitTo
  • track by
  • bind once
  • infinite scroll

binding

ng-bind vs {{}}

  • ng-bind is faster because it’s simpler in implementation
  • {{}} has to go through extra steps of verifying context, jsonification of values and more
ng-bind is faster than {{}}

{{}} time

one time binding

 <p>One time binding {{::name}}</p>
 <p>Normal binding {{name}}</p>
						
  • dont watch after first bind
  • Reducing the number of expressions being watched
  • digest loop gets faster
  • if value is undefined, keep watching
  • after getting a value, watcher will be deregistered
one time binding

when to use it

  • interpolating text or attributes:
<div name="attr: {{::color}}">text: {{::name}}</div>
						
a directive with bidirectional binding and the parameters will not change:
<div some-directive name="::myName" color="My color is {{::myColor}}"></div>
							
a directive that takes an expression:
<ul>
  <li ng-repeat="item in ::items">{{item.name}};</li>
</ul>
						

Digest cycle

digest cycle

  • any changes via view/service input
  • will trigger a $digest cycle
  • More data bindings creates
  • more watchers and scope objects
  • will take longer on each digest cycle
read section 9

Each time a Model is updated, either through user input in the View, or via service input to the Controller, Angular runs something called a $digest cycle.

This cycle is an internal execution loop that runs through your entire application’s bindings and checks if any values have changed. If values have changed, Angular will also update any values in the Model to return to a clear internal state.

When we create data-bindings with AngularJS, we’re creating more $$watchers and $scope Objects, which in turn will take longer to process on each $digest.

debounce digest

<input ng-model="name" ng-model-options="{ debounce: 250 }" />
						
show it in demo delay digest cycle

Outside Changes

$apply

  • a Model change has occurred outside of angular
  • $scope.$apply to inform Angular about change
  • use it correctly, in the past and thrown uncaught errors in my JavaScrip
  • $scope.$apply kicks off the $digest loop
  • digest loop in the rootscope
  • this means everything: dirty checked will run
  • Everything means: Entire application: all of the watchers
$(elem).myPlugin({
  onchange: function (newValue) {
    // model changes outside of Angular
    $(this).val(newValue);
    // tell Angular values have changed and to update via $digest
    $scope.$apply();
  }
});
							
code example in the middle

use scope.$digest for performance

  • $apply - trigger watchers on the entire $scope chain
  • $digest() method will only trigger watchers on the current $scope and its children
  • Show demo | digest.html
  • Only problem: if you have anything on the parent, that will not get updated
scope.digest as perf imp

watcher

how angular updates

  • remembers the value and compares it to previous value
  • basic dirty-checking
  • If there is a change in value, then it fires the change event.
  • not everything in scope gets watch

Implicit watcher

  • {{myExpression}}
  • ng-repeat
  • ng-show, ng-hide
  • ng-if, ng-switch

A function gets added to the $$watchers array

Every time $watch is called on a scope value, or a value is bound from the DOM with interpolation, an ng-repeat, an ng-switch, and ng-if, or any other DOM attribute/element, a function gets added to the $$watchers array of the innermost scope.

explicit watcher

$watch(watchExpression, listener, [objectEquality]);
						
  • will notify changes
  • update changes to the DOM
  • whatever is the first argument will be executed, many many times
  • it will compare values by reference
  • but if the third argument is "true"
  • deep - recurse through object with deep comparison
  • slow recurses- make a deep copy each time watched, to compare

avoid deep watch each time possible

Watching by value (scope.$watch (watchExpression, listener, true)) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.

Angular provides the ability to watch entire objects by passing a third, optional true parameter to scope.$watch. This is, for lack of better words, a terrible idea. A much better solution is to rely on services and object references to propagate object changes between scopes.

watchCollection

$watchCollection(obj, listener);
						
watch performance consideration

count watcher

use this explanation style

nice explanation count number of watch

do as little as possible

  • Avoid binding function directly to ng-show, ng-repeat, etc.
  • Avoid watch a function result directly
  • This function will run on every digest cycle
  • will slow your application
Misko answer

aggregate watcher

  • log viewer word watch
  • visit link below
optimization 2: 1200ms-to-35ms

All that time spent invoking change watchers was mostly wasted.

To address this, we created a directive that “hides” the change watchers of its children, allowing them to be invoked only when the value of a specified parent expression changes. With this change, we avoided invoking thousands of per-word change watchers on every mouse click or other minor event.

remove unneeded watcher

var unbinder = scope.$watch('scopeValueToBeWatcher', function(newVal, oldVal){
	//do something
});


unbinder(); //this line removes the watch from $$watchers.
						
check this one

watch only what needed

sometimes, deep watch is needed but not the whole object strip out irrelevant data to make it faster.
$scope.$watch(‘listOfBigObjects’, myHandler, true);
						
$scope.$watch(function($scope) {
  return $scope.listOfBigObjects.
      map(function(bigObject) {
        return bigObject.foo.fieldICareAbout;
      });
}, myHandler, true);
						

directive

directive execution

  • when directive inside ng repeat
  • compile is called only once
  • link and constructor is called once per iteration
myApp.directive(function() {
  return {
    link: function(s, elem, attr) {
      s.foo = scope.$eval(attr.foo);
      scope.$watch(attr.list, function(list) {
            // do something
          }, true);
    }};
});
						
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
        });
      };
    }
  };
});

						
so put as much as possible inside compile

Extra

broadcast vs emit

  • $rootScope.$emit - only $rootScope listeners. don't want every $scope to get it
  • $rootScope.$broadcast - every scope hear it
  • $scope.$emit - scope and all parents including rootscope hear it
  • $scope.$broadcast - itself and children
talk about emit, broadcast and bootstrap $rootScope.$emit only lets other $rootScope listeners catch it. This is good when you don't want every $scope to get it. Mostly a high level communication. Think of it as adults talking to each other in a room so the kids can't hear them. $rootScope.$broadcast is a method that lets pretty much everything hear it. This would be the equivalent of parents yelling that dinner is ready so everyone in the house hears it. $scope.$emit is when you want that $scope and all it's parents and $rootScope to hear the event. This is a child whining to their parents at home (but not at a grocery store where other kids can hear). $scope.$broadcast is for the $scope itself and its children. This is a child whispering to its stuffed animals so their parents can't hear. $emit The event life cycle starts at the scope on which $emit was called. All listeners listening for name event on this scope get notified. Afterwards, the event traverses upwards toward the root scope and calls all registered listeners along the way. The event will stop propagating if one of the listeners cancels it. $broadcast Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners. The event life cycle starts at the scope on which $broadcast was called. All listeners listening for name event on this scope get notified. Afterwards, the event propagates to all direct and indirect scopes of the current scope and calls all registered listeners along the way. The event cannot be canceled

performance talk

  • $parse vs $eval vs interpolation
  • parse: tokenise
  • better: call parse once. save it
  • and do eval everytime u needed
ng conf video on performance

ng-class vs ng-style

  • ng-class demo
  • ng-style demo
  • delay is due to Angular’s reliance on .data method to store all sort of data such as a class-count map and more
  • if you have large data. you can consider it
ng style vs ng class

Implementation detail

  • Angular’s watcher functions for ng-bind and text nodes ({{expression}}) put binding information inside the respective DOM elements using jQuery .data method
  • expensive operation that impacts both load times and time taken to delete nodes
  • this information is not used in actual binding and is mostly for debugging purposes
  • load time reduced by as much as 50%
//from angular 1.3
config(['$routeProvider', '$compileProvider', function($routeProvider, $compileProvider) {
    $compileProvider.debugInfoEnabled(false);
}]
						
ng-perf.com

others

ng-react directive with example

summary

summary

  • bind once or one time binding
  • prefer filter in the controller
  • pagination, limitTo, track by
  • $destroy, unbindwatch
  • prefer digest over apply
  • Everything doesnt have to be angular: ng-react

How to check perfomance

(if no wifi)

  • pageSpeed

Free tip

Look Busy at Work

Ctrl + 1 when big brother is around Always carry extra bag and print out do ur personal work, but leave after ur manager always leave a extra jacket on your chair compose email during office hours, send it midnight or weekends Look busy

goo.gl/9ylsTB

www.thatJSdude.com / @mdkhan005

(youtube.com/user/khanLearning)