Skip to content

milkdeliver/brian-talks-about-angular-with-lots-of-data

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

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="prev -= 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

About

Lightning talk about making Angular apps that deal with lots of data fast

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages