Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Lightning talk about making Angular apps that deal with lots of data fast
branch: master

Merge pull request #2 from jacobmumm/master

fix: "previous page" variable typo in example
latest commit e9af527a43
Brian Ford authored
Failed to load latest commit information.
README.md Update README.md

README.md

brian talks about angular with lots of data

This is my lightning talk about performance considerations for Angular apps displaying lots of data.

It starts with a simplified explanation of $digest and then gives a few examples of how to apply this knowledge to cases when you're repeating over a bunch of elements.

Measure things

Make sure you know what's slow. Lots of tools that can help.

I'm not going to talk about them.

What factors are important?

What causes slowness?

DOM manipulation

Angular is pretty good at helping you with this. In some special cases you can write your own directive to manipulate the DOM and improve performance.

dynamic things

The number of "dynamic things" your user sees. This is called the number of $watchs.

Counting Dynamic Things

What adds a $watch?

Consider the following chunk of an app:

<div>
  <p>{{thing}}</p>
  <p>{{getThing()}}</p>

  <div ng-repeat="item in items">{{item.name}}</div>
</div>

Here's a list of the things Angular $watchs for updates:

// angular figures this out based on directives, which add `$watch`s
var thingsThatMightUpdate = [
  $scope.thing,
  $scope.getThing(), // invoke this each time

  // from ngRepeat
  $scope.items.length,
  $scope.items[0], // obj refs
  $scope.items[1],
  $scope.items[2],
  $scope.items[3],

  // from {{item.name}} inside ngRepeat
  $scope.items[0].name, // values
  $scope.items[1].name,
  $scope.items[2].name,
  $scope.items[3].name,
];

Digest cycle

This is a simplification (cuz lightning talk). The documentation on scopes have the full story.

// map thingName to value
var oldValues = {
  '$scope.thing': 'hello',
  '$scope.items.length': 4,
  /* ... */
};

function digest () {
  var thingsThatChanged;
  do {
    thingsThatChanged = [];

    thingsThatMightUpdate.forEach(function (thing) {
      if (oldValue[thingName] !== $scope[thingName]) {
        thingsThatChanged.push(thingName);
        oldValue[thingName] = $scope[thingName];
      }
    });

    // trigger `$watch`s, updating the DOM, etc

  } while(thingsThatChanged.length > 0);
}

Note that we have to eval $scope.getThing() on each digest. If getThing() is slow, then all your digests are slow.

If you have a bunch of things in the array, the digest will be slow.

tl;dr – watch fewer things, keep $watchs fast.

How to reduce expensive watches

$$$$$$$$$$$$$$$$$$$$$

Transform the data before binding

Rather than use filters/functions in your bindings, transform the data before binding to it.

Before

Controller:

angular.module('mySlowApp', []).controller('MyController', [
  '$scope', '$http', function ($scope, $http) {

    // this has like a million elts
    $http.get('all-the-things.json').success(function (data) {
      $scope.data = data;
    });

    $scope.transform = function (item) {
      // do something expensive
      return item;
    };
  }]);

HTML:

<div ng-repeat="item in data">{{transform(item.name)}}</div>

After

Controller:

angular.module('myFastApp', []).controller('MyController', [
  '$scope', '$http', function ($scope, $http) {

    // this has like a million elts
    $http.get('all-the-things.json').success(function (data) {
      $scope.data = data.map(transform);
    });

    function transform (item) {
      // do something expensive
      return item;
    };
  }]);

HTML:

<div ng-repeat="item in data">{{item.name}}</div>

How to watch fewer things

Here are a few strategies.

Paginate

ngRepeat over a subset of the things.

Controller:

angular.module('myApp', []).controller('MyController', [
  '$scope', function ($scope) {

    // this has like a million elts
    $scope.bigArray = [ /* ... */ ];

    $scope.page = 0;

    $scope.$watch('page', function (index) {
      $scope.littleArray = $scope.bigArray.slice(index, index + 5);
    });

  }]);

Template:

<div>
  <div ng-repeat="item in littleArray">{{item.name}}</div>
  <button ng-click="page -= 1">prev</button>
  <button ng-click="page += 1">next</button>
</div>

You can use a similar strategy but with infinite/virtual scrolling.

You know the data is populated once

~Coming to 1.3 Soon~

~3rd Party Modules~

You know the data is populated occasionally

Opt out of data binding; use event listeners.

In your controller:

angular.module('myApp', []).controller('MyController', [
  '$scope', '$interval', '$http', function ($scope, $interval, $http) {

    // refresh every 10 seconds
    $interval(function () {
      // this has like a million elts
      $http.get('all-the-things.json').success(function (data) {
        $scope.$broadcast('dataUpdated', data);
      });
    }, 10000);

  }]);

In your directive:

{
  link: function (scope, elt) {
    scope.$on('dataUpdated', function (ev, data) {
      // do DOM manipulation
    });
  }
}

Summary

Angular makes the 95% case fast, but gives you the tools to take control of your own performance destiny when you want to.

License

MIT

Something went wrong with that request. Please try again.