Lesson 7: Screaming Fast AngularJS

In this Lesson, we will cover the following recipes:

  • Recognizing AngularJS landmines
  • Creating a universal watch callback
  • Inspecting your application's watchers
  • Deploying and managing $watch types efficiently
  • Optimizing the application using reference $watch
  • Optimizing the application using equality $watch
  • Optimizing the application using $watchCollection
  • Optimizing the application using $watch deregistration
  • Optimizing template-binding watch expressions
  • Optimizing the application with the compile phase in ng-repeat
  • Optimizing the application using track by in ng-repeat
  • Trimming down watched models

Introduction

As with most technologies, in AngularJS, the devil is in the details.

In general, the lion's share of encounters with AngularJS's sluggishness is a result of overloading the application's data-binding bandwidth. Doing so is quite easy, and a normative production application contains a substantial amount of data binding, which makes architecting a snappy application all the more difficult. Thankfully, for all the difficulties and snags that one can encounter involving scaled data binding, the use of regimented best practices and gaining an appreciation of the underlying framework structure will allow you to effectively circumnavigate performance pitfalls.

Recognizing AngularJS landmines

Implementation of configurations and combinations that lead to severe performance degradation is often difficult to pinpoint as the contributing components by themselves often appear to be totally innocuous.

How to do it…

The following scenarios are just a handful of the commonly encountered scenarios that degrade the application's performance and responsiveness.

Expensive filters in ng-repeat

Filters will be executed every single time the enumerable collection detects a change, as shown here:

<div ng-repeat="val in values | filter:slowFilter"></div>

Building and using filters that require a great deal of processing is not advisable as you must assume that filters will be called a huge number of times throughout the life of the application.

Deep watching a large object

You might find it tempting to create a scope watcher that evaluates the entirety of a model object; this is accomplished by passing in true as the final argument, as shown here:

$scope.$watch(giganticObject, function() { ... }, true);

This is a poor design decision as AngularJS needs to be able to determine whether or not the object has changed between $digest cycles, which of course means storing a history of the object's exact value, as well as exhaustively comparing it each time.

Using $watchCollection when the index of change is needed

Although it is extremely convenient in a number of scenarios, $watchCollection can trap you if you try to locate the index of change within it. Consider the following code:

$scope.$watchCollection(giganticArray, function(newVal, oldVal, scope) {
  var count = 0;
  // iterate through newVal array
  angular.forEach(newVal, function(oldVal) {
    // if the array snapshot index doesn't match,
    // this implies a change in model value
    if (newVal[count] !== oldVal[count]) {
      // logic for matched object delta
    }
    count++;
  });
});

In every $digest cycle, the watcher will iterate through each watched array in order to find the index/indices that have changed. Since this watcher is expected to be invoked quite often, this approach has the potential to introduce performance-related problems as the watched collection grows.

Keeping template watchers under control

Each bound expression in a template will register its own watch list entry in order to keep the data fully bound to the view. Suppose that you were working with data in a 2D grid, as follows:

<div ng-repeat="row in rows">
  <div ng-repeat="val in row">
    {{ val }}
  </div>
</div>

Assuming that rows is an array of arrays, this template fragment creates a watcher for every individual element in the 2D array. Since watch lists are processed linearly, this approach obviously has the potential to severely degrade the application's performance.

There's more…

These are only a handful of scenarios that can cause problems for your application. There is a virtually unlimited number of possible configurations that can cause an unexpected slowdown in your application, but being vigilant and watching out for common performance anti-patterns will ameliorate much of the headache that comes along with debugging the slowness of an application.

See also

  • The Creating a universal watch callback recipe provides the details of how to keep track of how often your application's watchers are being invoked
  • The Inspecting your application's watchers recipe shows you how to inspect the internals of your application in order to find where your watchers are concentrated
  • The Deploying and managing $watch types efficiently recipe describes the methods for keeping your application's watch bloat under control

Creating a universal watch callback

Since a multiplicity of AngularJS watchers is so commonly the root cause of performance problems, it is quite valuable to be able to monitor your application's watch list and activity. Few beginner level AngularJS developers realize just how often the framework is doing the dirty checking for them, and having a tool that gives them direct insight into when the framework is spending time to perform model history comparisons can be extremely useful.

How to do it…

The $scope.$watch(), $scope.$watchGroup(), and $scope.$watchCollection() methods are normally keyed with a stringified object path, which becomes the target of the change listener. However, if you wish to register a callback for any watch callback irrespective of the change listener target, you can decline to provide a change listener target, as follows:

// invoked once every time $scope.foo is modified
$scope.$watch('foo', function(newVal, oldVal, scope) {
  // newVal is the current value of $scope.foo
  // oldVal is the previous value of $scope.foo
  // scope === $scope
});

// invoked once every time $scope.bar is modified
$scope.$watch('bar', function(newVal, oldVal, scope) {
  // newVal is the current value of $scope.bar
  // oldVal is the previous value of $scope.bar
  // scope === $scope
});

// invoked once every $digest cycle
$scope.$watch(function(scope) {
     // scope === $scope
});

How it works…

There's no trickery here; the universal watcher is a feature that is explicitly provided by AngularJS. Although it invokes $watch() on a scope object, the callback will be executed for every model's modification, independent of the scope upon which it is defined.

There's more…

Although the watch callback will occur for model modifications anywhere, the lone scope parameter for the callback will always be the scope upon which the watcher was defined, not the scope in which the modification occurred.

Note

Since using a universal watcher attaches additional logic to every $digest cycle, it will severely degrade the application's performance and should only be used for debugging purposes.

See also

  • The Inspecting your application's watchers recipe shows you how to inspect the internals of your application in order to find where your watchers are concentrated
  • The Deploying and managing $watch types efficiently recipe describes the methods to keep your application's watch bloat under control

Inspecting your application's watchers

The Batarang browser plugin allows you to inspect the application's watch tree, but there are many scenarios where dynamically inspecting the watch list within the console or application code can be more helpful when debugging or making design decisions.

How to do it…

The following function can be used to inspect all or part of the DOM for watchers. It accepts an optional DOM element as an argument.

var getWatchers = function (element) {
  // convert to a jqLite/jQuery element
  // angular.element is idempotent
  var el = angular.element(
      // defaults to the body element
      element || document.getElementsByTagName('body')
    )
    // extract the DOM element data
    , elData = el.data()
    // initalize returned watchers array
    , watchers = [];

  // AngularJS lists watches in 3 categories
  // each contains an independent watch list
  angular.forEach([
      // general inherited scope
      elData.$scope,
      // isolate scope attached to templated directive
      elData.$isolateScope,
      // isolate scope attached to templateless directive
      elData.$isolateScopeNoTemplate
    ],
    function (scope) {
      // each element may not have a scope class attached
      if (scope) {
        // attach the watch list
        watchers = watchers.concat(scope.$$watchers || []);
      }
    }
  );

  // recurse through DOM tree
  angular.forEach(el.children(), function (childEl) {
    watchers = watchers.concat(getWatchers(childEl));
  });

  return watchers;
};

With this, you are able to call the function with a DOM node and ascertain which watchers exist inside it, as follows:

// all watchers in the document
getWatchers(document);

// all watchers in the signup form with a selector
getWatchers(document.getElementById('signup-form'));

// all watchers in <div class="container"></div>
getWatchers($('div.container'));

How it works…

It is possible to access a DOM element's $scope object (without injecting it) through the jQuery/jqLite element object's data() method. The $scope object has a $$watchers property that lists how many watchers are actively defined upon that $scope object.

The preceding function exhaustively recurses through the DOM tree and inspects each node in order to determine whether it has a scope attached to it. If it does, any watchers defined on that scope are read and entered into the master watch list.

There's more…

This is only a single, general implementation of watcher inspection. Since watchers are localized to a single scope, it might behoove you to utilize components of this function in order to inspect single scope instances instead of the child DOM subtree.

See also

  • The Recognizing AngularJS landmines recipe demonstrates common performance-leeching scenarios
  • The Creating a universal watch callback recipe provides the details of how to keep track of how often your application's watchers are being invoked
  • The Deploying and managing $watch types efficiently recipe describes the methods for keeping your application's watch bloat under control

Deploying and managing $watch types efficiently

The beast behind AngularJS's data binding is its dirty checking and the overhead that comes along with it. As you tease apart your application's innards, you will find that even the most elegantly architected applications incur a substantial amount of dirty checking. This, of course, is normal, and the framework is architected as to be able to handle the hugely variable loads of dirty checking that different sorts of applications might throw at it. Nevertheless, the nature of object comparison performance at scale (hint—it is slow) requires that dirty checking is minimally deployed, efficiently organized, and appropriately targeted. Even with the rigorous engineering and optimization behind AngularJS's dirty checking, it remains the case that it is still deceptively easy to bog down an application's performance with superfluous data comparison. In the same way that a single uncooperative person backpaddling in a canoe can bring a vessel to a halt, a single careless watch statement can bring an AngularJS application's responsiveness to its knees.

How to do it…

Strategies to deploy watchers efficiently can be summed up as follows.

Watch as little of the model as possible

Watchers check the portion of the model they are bound to extremely frequently. If a change in a piece of the model does not affect what the watch callback does, then the watcher shouldn't need to worry about it.

Keep watch expressions as lightweight as possible

The watch expression $scope.$watch(' myWatchExpression ', function() {}); will be evaluated in every digest cycle in order to determine the output. You'll be able to put expressions such as 3 + 6 or myFunc() as the expression, but these will be evaluated in every single digest cycle in an effort to obtain a fresh return value in order to compare it against the last recorded return value. Very rarely is this necessary, so stick to binding watchers to model properties.

Use the fewest number of watchers possible

It stands to reason that, as the entire watch list must be evaluated in every $digest cycle, fewer watchers in that list will yield a speedier $digest cycle.

Keep the watch callbacks small and light

The watch callbacks get called as often as the watch expression changes, which can be quite a lot depending on the application. As a result, it is unwise to keep high-latency calculations or requests in the callback.

Create DRY watchers

Though unrelated to performance, maintaining huge groups of watchers can become extremely tedious. The $watchCollection and $watchGroup utilities provided by AngularJS greatly assist in watcher consolidation.

See also

  • The Recognizing AngularJS landmines recipe demonstrates common performance-leeching scenarios
  • The Optimizing the application using reference $watch recipe demonstrates how to effectively deploy the basic watch type
  • The Optimizing the application using equality $watch recipe demonstrates how to effectively deploy the deep watch type
  • The Optimizing the application using $watchCollection recipe demonstrates how to utilize the intermediate depth watcher in your application
  • The Optimizing the application using $watch deregistration recipe shows how your application can evict watch list entries when they are no longer required
  • The Optimizing template-binding watch expressions recipe explains how AngularJS manages your implicitly-created watchers for template data binding

Optimizing the application using reference $watch

Reference watches register a listener that uses strict equality (===) as the comparator, which verifies the congruent object identity or primitive equality. The implication of this is that a change will only be registered if the model the watcher is listening to is assigned to a new object.

How to do it…

The reference watcher should be used when the object's properties are unimportant. It is the most efficient of the $watch types as it only demands top-level object comparison.

The watcher can be created as follows:

$scope.myObj = {
  myPrim: 'Go Bears!',
  myArr: [3,1,4,1,5,9]
};

// watch myObj by reference
$scope.$watch('myObj', function(newVal, oldVal, scope) {
  // callback logic
});

// watch only the myPrim property of myObj by reference
$scope.$watch('myObj.myPrim', function(newVal, oldVal, scope) {
  // callback logic
});

// watch only the second element of myObj.myArr by reference
$scope.$watch('myObj.myArr[1]', function(newVal, oldVal, scope) {
  // callback logic
});

Note

An observant reader will note that some of these examples are technically redundant in what they demonstrate; this will be explained further in the How it works… section.

How it works…

The reference comparator will only invoke the watch callback upon object reassignment.

Suppose that a $scope object was initialized as follows:

$scope.myObj = {
  myPrim: 'Go Bears!'
};
$scope.myArr = [3,1,4,1,5,9];

// watch myObj by reference
$scope.$watch('myObj', function() {
  // callback logic
});
// watch myArr by reference
$scope.$watch('myArr', function() {
  // callback logic
});

Any assignment of the watched object to a different primitive or object will register as dirty. The following examples will cause a callback to execute:

$scope.myArr = [];
$scope.myObj = 1;
$scope.myObj = {};

Beneath the top-level reference watching, any changes that affect the inside of the object will not register as changes. This includes modification, creation, and deletion. The following will not cause the callback to execute:

// replace existing property
$scope.myObj.myPrim = 'Go Giants!';

// add new property
$scope.myObj.newProp = {};

// push onto array
$scope.myArr.push(2);

// modify element of array
$scope.myArr[0] = 6;

// delete property
delete myObj.myPrim;

There's more…

The long and short of it is that reference watchers are the most efficient type of watchers, so when you are looking to set up a watcher, reach for this one first.

See also

  • The Optimizing the application using equality $watch recipe demonstrates how to effectively deploy the deep watch type
  • The Optimizing the application using $watchCollection recipe demonstrates how to utilize the intermediate depth watcher in your application
  • The Optimizing the application using $watch deregistration recipe shows how your application can evict watch list entries when no longer required

Optimizing the application using equality $watch

Equality watches register a listener that uses angular.equals() as the comparator, which exhaustively examines the entirety of all objects to ensure that their respective object hierarchies are identical. Both a new object assignment and property modification will register as a change and invoke the watch callback.

This watcher should be used when any modification to an object is considered as a change event, such as a user object having its properties at various depths modified.

How to do it…

The equality comparator is used when the optional Boolean third argument is set to true. Other than that, these watchers are syntactically identical to reference comparator watchers, as shown here:

$scope.myObj = {
  myPrim: 'Go Bears!',
  myArr: [3,1,4,1,5,9]
};

// watch myObj by equality
$scope.$watch('myObj', function(newVal, oldVal, scope) {
  // callback logic
}, true);

How it works…

The equality comparator will invoke the watch callback on every modification anywhere on or inside the watched object.

Suppose that a $scope object is initialized as follows:

$scope.myObj = {
  myPrim: 'Go Bears!'
};
$scope.myArr = [3,1,4,1,5,9];

// watch myObj by equality
$scope.$watch('myObj', function() {
  // callback logic
}, true);
// watch myArr by equality
$scope.$watch('myArr', function() {
  // callback logic
}, true);

All of the following examples will cause a callback to be executed:

$scope.myArr = [];
$scope.myObj = 1;
$scope.myObj = {};
$scope.myObj.myPrim = 'Go Giants!';
$scope.myObj.newProp = {};
$scope.myArr.push(2);
$scope.myArr[0] = 6;
delete myObj.myPrim;

There's more…

Since a watcher must store the past version of the watched object to compare against it and perform the actual comparison, equality watchers utilize both the angular.copy() method to store the object and the angular.equals() method to test the equality. For large objects, it is not difficult to discern that these operations will introduce latency into the application. Equality comparator watchers should not be used unless absolutely necessary.

See also

  • The Optimizing the application using reference $watch recipe demonstrates how to effectively deploy the basic watch type
  • The Optimizing the application using $watchCollection recipe demonstrates how to utilize the intermediate depth watcher in your application
  • The Optimizing the application using $watch deregistration recipe shows how your application can evict watch list entries when they are no longer required

Optimizing the application using $watchCollection

AngularJS offers the $watchCollection intermediate watch type to register a listener that utilizes a shallow watch depth for comparison. The $watchCollection type will register a change event when any of the object's properties are modified, but it is unconcerned with what those properties refer to.

How to do it…

This watcher is best used with arrays or flat objects that undergo frequent top-level property modifications or reassignments. Currently, it does not provide the modified property(s) responsible for the callback, only the entire objects, so the callback is responsible for determining which properties or indices are incongruent. This can be done as follows:

$scope.myObj = {
  myPrimitive: 'Go Bears!',
  myArray: [3,1,4,1,5,9]
};

// watch myObj and all top-level properties by reference
$scope.$watchCollection('myObj', function(newVal, oldVal, scope) {
  // callback logic
});

// watch myObj.myArr and all its elements by reference
$scope.$watchCollection('myObj.myArr', function(newVal, oldVal, scope) {
  // callback logic
});

How it works…

The $watchCollection utility will set up reference watchers on the model object and all its existing properties. This will invoke the watch callback upon object reassignment or upon top-level property reassignment.

Suppose that a $scope object is initialized as follows:

$scope.myObj = {
  myPrim: 'Go Bears!',
  innerObj: {
    innerProp: 'Go Bulls!'
  }
};
$scope.myArr = [3,1,4,1,5,9];

// watch myObj as a collection
$scope.$watchCollection('myObj', function() {
  // callback logic
});
// watch myArr as a collection
$scope.$watchCollection('myArr', function() {
  // callback logic
});

The following examples will cause a callback to be executed:

// object reassignment
$scope.myArr = [];
$scope.myObj = 1;
$scope.myObj = {};

// top-level property reassignment
$scope.myObj.myPrim = 'Go Giants!';

// array element reassignment
$scope.myArr[0] = 6;

// deletion of top level property
delete myObj.myPrim;

The following will not cause the callback to be executed:

// add new property
$scope.myObj.newProp = {};

// push new element onto array
$scope.myArr.push(2);

// modify, create, or delete nested property
$scope.myObj.innerObj.innerProp = 'Go Blackhawks!';
$scope.myObj.innerObj.otherProp = 'Go Sox!';
delete $scope.myObj.innerObj.innerProp;

There's more…

The name $watchCollection is a bit deceptive (depending on how you think about enumerable collections in JavaScript) as it might not perform how you would expect—especially since it doesn't watch for elements that are being added to the collection. Since explicitly-defined properties and array indices are effectively identical at the object property level, $watchCollection is really more of a single-depth reference watcher.

See also

  • The Deploying and managing $watch types efficiently recipe describes methods to keep your application's watch bloat under control
  • The Optimizing the application using reference $watch recipe demonstrates how to effectively deploy the basic watch type
  • The Optimizing the application using equality $watch recipe demonstrates how to effectively deploy the deep watch type
  • The Optimizing the application using $watch deregistration recipe shows how your application can evict watch list entries when they are no longer required

Optimizing the application using $watch deregistration

Nothing boosts watcher performance quite like destroying the watcher altogether. Should you encounter a scenario where you no longer have a need to watch a model component, invoking watch creation returns a deregistration function that will unbind that watcher when called.

How to do it…

When a watcher is initialized, it will return its deregistration function. You must store this deregistration function until it needs to be invoked. This can be done as follows:

$scope.myObj = {}

// watch myObj by reference
var deregister = $scope.$watch('myObj', function(newVal, oldVal, scope) {
  // callback logic
});

// prevent additional modifications from invoking the callback
deregister();

How it works…

The $watch destruction will normally be needed when a change in application state causes a watch to no longer be useful while the scope that it is defined inside still exists. When a scope is destroyed—either manually or automatically—the watchers defined upon it will be flagged as eligible for garbage collection, and therefore, manual teardown is not required.

However, this is contingent upon the scope on which the watcher is destroyed. If your application has watchers defined on a parent scope or $rootScope, they will not be flagged for garbage collection and must be destroyed manually upon scope destruction (usually accomplished with $scope.$on('$destroy', function() {})), or else your application is subject to potential memory leaks in the form of orphaned watchers.

See also

  • The Deploying and managing $watch types efficiently recipe describes methods to keep your application's watch bloat under control
  • The Optimizing the application using reference $watch recipe demonstrates how to effectively deploy the basic watch type
  • The Optimizing the application using equality $watch recipe demonstrates how to effectively deploy the deep watch type
  • The Optimizing the application using $watchCollection recipe demonstrates how to utilize the intermediate depth watcher in your application

Optimizing template-binding watch expressions

Any AngularJS template expression inside double braces ({{ }}) will register an equality watcher using the enclosed AngularJS expression upon compilation.

How to do it…

Curly braces are easily recognized as the AngularJS syntax for template data binding. The following is an example:

<div ng-show="{{myFunc()}}">
  {{ myObj }}
</div>

On a high level, even to a beginner level AngularJS developer, this is painfully obvious.

Interpolating the two preceding expressions into the view implicitly creates two watchers for each of these expressions. The corresponding watchers will be approximately equivalent to the following:

$scope.$watch('myFunc()', function() { ... }, true);
$scope.$watch('myObj', function() { ... }, true);

How it works…

The AngularJS expression contained within {{ }} in the template will be the exact entry registered in the watch list. Any method or logic within that expression will necessarily be evaluated for its return value every time dirty checking is performed. An observant developer will note that any logic contained in myFunc() will be evaluated on every single digest cycle, which can degrade the performance extremely rapidly. Therefore, it will benefit your application greatly to have the value of the watch entry calculable as quickly as possible. An easy way to accomplish this is to not provide methods or logic as expressions at all, but to calculate the output of the method and store it in a model property, which can then be passed to the template.

There's more…

Template watch entries have setup and teardown processes automatically taken care of for you. You must be careful though, as using {{ }} in your template will sneakily cause your watch count to balloon. AngularJS 1.3 introduces bind once capabilities, which allow you to interpolate model data into the view upon compilation, but not to bring along the overhead of data binding, if it will not be necessary.

See also

  • The Inspecting your application's watchers recipe shows you how to inspect the internals of your application to find where your watchers are concentrated
  • The Deploying and managing $watch types efficiently recipe describes methods to keep your application's watch bloat under control
  • The Optimizing the application with the compile phase in ng-repeat recipe demonstrates how to reduce redundant processing inside repeaters
  • The Optimizing the application using track by in ng-repeat recipe demonstrates how to configure your application to prevent unnecessary rendering inside a repeater
  • The Trimming down watched models recipe provides the details of how you can consolidate deep-watched models to reduce comparison and copy latency

Optimizing the application with the compile phase in ng-repeat

An extremely common pattern in an AngularJS application is to have an ng-repeat directive instance spit out a list of child directives corresponding to an enumerable collection. This pattern can obviously lead to performance problems at scale, especially as directive complexity increases. One of the best ways to curb directive processing bloat is to eliminate any processing redundancy by migrating it to the compile phase.

Getting ready

Suppose that your application contains the following pseudo-setup. This is what we need for the next section:

(index.html)

<div ng-repeat="element in largeCollection">
  <span my-directive></span>
</div>

(app.js)

angular.module('myApp', [])
.directive('myDirective', function() {
  return {
    link: function(scope, el, attrs) {
      // general directive logic and initialization
      // instance-specific logic and initialization
    }
  };
});

How to do it…

A clever developer will note that since a directive's link function executes once for each instance of the directive in the repeater, the current implementation is wasting time performing the same actions for each instance.

Since the compile phase will only occur once for all directives inside an ng-repeat directive, it makes sense to perform all generalized logic and initialization within that phase, and share the results with the returned link function. This can be done as follows:

(app.js)

angular.module('myApp', [])
.directive('myDirective', function() {
  return {
    compile: function(el, attrs) {
      // general directive logic and initialization
      return function link(scope, el, attrs) {
        // instance-specific logic and initialization
        // link function closure can access compile vars
      };
    }
  };
});

How it works…

The ng-repeat directive will implicitly reuse the same compile function for all the directive instances it creates. Therefore, it's a no-brainer that any redundant processing done inside link functions should be moved to the compile function as far as possible.

There's more…

This is by no means a fix all for the sluggishness of ng-repeat, as high latency can stem from a large number of common problems when iterating through huge amounts of bound data. However, using the compile phase effectively is an often overlooked strategy that has the potential to yield huge performance gains from a relatively simple refactoring.

Furthermore, even though this condenses logic into a single compile phase per ng-repeat, the compile logic will still get executed once for every instance of the directive in the template. If you truly want the logic to only get executed once for the entire application, use the fact that service types are singletons to your advantage, and migrate the logic inside one of them.

See also

  • The Recognizing AngularJS landmines recipe demonstrates common performance-leeching scenarios
  • The Deploying and managing $watch types efficiently recipe describes methods to keep your application's watch bloat under control
  • The Optimizing the application using track by in ng-repeat recipe demonstrates how to configure your application to prevent unnecessary rendering inside a repeater
  • The Trimming down watched models recipe provides the details of how you can consolidate deep-watched models to reduce comparison and copy latency

Optimizing the application using track by in ng-repeat

By default, ng-repeat creates a DOM node for each item in the collection and destroys that DOM node when the item is removed. It is often the case that this is suboptimal for your application's performance, as a constant stream of re-rendering a sizeable collection will rarely be necessary at the repeater level and will tax your application's performance heavily. The solution is to utilize the track by expression, which allows you to define how AngularJS associates DOM nodes with the elements of the collection.

How to do it…

When track by $index is used as an addendum to the repeat expression, AngularJS will reuse any existing DOM nodes instead of re-rendering them.

The original, suboptimal version is as follows:

<div ng-repeat="element in largeCollection">
  <!-- element repeater content -->
</div>

The optimized version is as follows:

<div ng-repeat="element in largeCollection track by $index">
  <!-- element repeater content -->
</div>

How it works…

By default, ng-repeat associates each collection element by reference to a DOM node. Using the track by expression allows you to customize what that association is referencing instead of the collection element itself. If the element is an object with a unique ID, that is suitable. Otherwise, each repeated element is provided with $index on its scope, which can be used to uniquely identify that element to the repeater. By doing this, the repeater will not destroy the DOM node unless the index changes.

See also

  • The Recognizing AngularJS landmines recipe demonstrates common performance-leeching scenarios
  • The Inspecting your application's watchers recipe shows you how to inspect the internals of your application to find where your watchers are concentrated
  • The Deploying and managing $watch types efficiently recipe describes methods to keep your application's watch bloat under control
  • The Optimizing the application with the compile phase in ng-repeat recipe demonstrates how to reduce redundant processing inside repeaters
  • The Trimming down watched models recipe provide the details of how you can consolidate deep-watched models to reduce comparison and copy latency

Trimming down watched models

The equality comparator watcher can be a fickle beast when tuning the application for better performance. It's always best to avoid it when possible, but of course, that holds true until you actually need to deep watch a collection of large objects. The overhead of watching a large object is so cumbersome that sometimes distilling objects down to a subset for the purposes of comparison can actually yield performance gains.

How to do it…

The following is the naïve method of an exhaustive equality comparator watch:

$scope.$watch('bigObjectArray', function() {
  // watch callback
}, true);

Instead of watching the entire object, it is possible to call map() on a collection of large objects in order to extract only the components of the objects that actually need to be watched. This can be done as follows:

$scope.$watch(
  // function that returns object to be watched
  function($scope) {
    // map the array to distill the relevant properties
    // this return value is what will be compared against
    return $scope.bigObjectArray.map(function(bigObject) {
      // return only the property we want
      return bigObject.relevantProperty;
    });
  },
  function(newVal, oldVal, scope) {
    // watch callback
  },
  // equality comparator
  true
);

How it works…

The $watch expression can be passed anything that it can compare to a past value; it does not have to be an AngularJS string expression. The outer function is evaluated for its return value, which is used as the value to compare against. For each cycle, the dirty checking mechanism will map the array, test it against the old value, and record the new value.

There's more…

If the time it takes to copy and compare the entire object array is greater than the time it takes to use map() on the array and compare the subsets, then using the watcher in this way will yield a performance boost.

See also

  • The Recognizing AngularJS landmines recipe demonstrates common performance-leeching scenarios
  • The Deploying and managing $watch types efficiently recipe describes the methods to keep your application's watch bloat under control
  • The Optimizing the application with the compile phase in ng-repeat recipe demonstrates how to reduce redundant processing inside repeaters
  • The Optimizing the application using track-by in ng-repeat recipe demonstrates how to configure your application to prevent unnecessary rendering inside a repeater
See also
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.68.49