Angular & Performance: Best Practices
Last modified: December 8, 2016
Despite the framework's popularity, Angular certainly has performance issues. While Angular 2 attempts to compensate for Angular 1's weaknesses, it's a complete rewrite of the framework and difficult to learn. This article explores both the performance issues surrounding Angular 1 and potential solutions that don't require jumping to Angular 2.
Understanding Angular's Digest Cycle
Angular's digest cycle is central to its two way data binding functionality. For every user action, the digest cycle recursively checks the DOM tree for $scope watchers. If these watchers change in value, their corresponding watcher functions are fired and the tree is checked again. This cycle is what keeps data models in sync with views, as each user event triggers a recursive check of every watcher value. This is known as 'dirty checking'.
These watchers include any $scope variable you define or any expression that you bind to the DOM via an 'ng' directive, etc. For example, if you use do something like:
you are creating a watcher function for the 'enabled' value. Every time the digest cycle runs it will check to see if the value of 'enabled' has changed. If so, that particular element will be added/removed from the DOM.
While the digest cycle makes two way data binding possible, it gets progressively slower with each additional watcher. Things can get out of hand fast, especially when you start using ng-repeat and other iterative approaches to drawing tables. Why? Because what ng-repeat really does is create a separate scope instance for each element in the iteration. If you combine this with nested repeats and additional watchers, things will slow down (and fast).
How to improve performance
Limit scope watchers
Based on how often the digest cycle runs, it's important to keep scope watchers to a minimum. Don't add $scope variables to your controller unless absolutely necessary. Remember that the digest cycle will check the current scope as well as any child scopes. Avoiding nested repeats and additional watcher functions is an easy way to keep watchers to a minimum.
Explore one way bindings
If you've played with Angular, then your familiar with the following syntax:
This interpolation directly ties your view to the scope vairable defined in your Angular.js controller. It's important to note that this too registers a watcher for the current scope. What may be less familliar to beginners is the following:
This is known as one way binding. It interpolates the initial value as defined in the scope, however it doesn't generate a watcher. Sure you lose the two way data binding, but if the data doesn't need to change again then there's no point in throwing it into the digest loop.
Use pagination and infinite scrolling
Many performance issues start to happen with larger data sets. If you're returning thousands of rows and using ng-repeat, that's going to be a lot of watchers. For these situations, it may be better to break up the data so that your only iterating through ten entries at a time. This will drastically reduce the time needed to draw tables using built in Angular directives (most namely ng-repeat).
It's important to understand the digest cycle when using Angular. Be sure to recognize when the cycle is running and how many watchers you're iterating through each time. While Angular makes it easy to quickly build dynamic client side apps, it will burn you on performance as your app scales. If you can limit watchers and keep controllers lite, you can still reap the benefits of Angular 1 without sacrificing performance (or moving to Angular 2).