5 Day Journey to Improve Performance of your AngularJS application

Recently I came across a project where I had to take on the job of reducing the total watcher count on a website where the total data set visible to the user was potentially infinite. As a bonus, this website had to be responsive and usable on any and all devices of all shapes and sizes — this post documents my 5 day journey to reduce the watcher count of an AngularJS 1.4 application to a managable state. Hopefully you are not in the same predicatment as me and are using Angular or React by this point – c’mon it’s 2018 – what are you waiting for?! But if you are not, I sympathize with you and encourage you to read on.

Day 1:

First and foremost, before embarking on the journey to reduce the overall watcher count — I had to arm myself with the right tools. After a bit of frantic googling — I came across the following two plugins that would help me get a grasp on how bloated the applications UI was with watchers and help to pinpoint which elements were the biggest offenders. I’d highly recommend installing these two Chrome plugins:

  1. Angular Total Watchers —  Total Watcher Count Plugin
  2. AngularJs Batarang- Detailed Watcher Breakdown Plugin

Day 2:

Armed with the toolset to get control of the watcher count, the next assignment was to blindly open as many tabs as possible for anything related to AngularJs watcher count reduction and start to compile knowledge. All a half day of research, I can safely say that I have compiled the best articles to help you understand Angular Watchers in the simplest terms possible.

  1. Perhaps the most valuable use of time to help you get your watcher count down will be to first understand how the Ins and Outs of AngularJS really work — especially the digest cycle — which is where all the watchers are evaluated. The brilliant Minko Gevchev has setup a skinny version of AngularJS on his github respository and written an invaluable post on understanding  bare bones of AngularJs . I would highly recommend checking out his GitHub repo for the lightweight AngularJS as well and set breakpoints to debug through the digest cycle to understand what happens under the hood of AngularJS. This will not only help you with reducing the watcher count on your page but also set you up for writing high performance code in your future AngularJS projects.
  2. Here are a few more valuable resources on understanding Dirty Checking and the digest cycle of AngularJS.

I understand that we are on day 2 of the Journey already and no code has been modified to reduce the watcher count but I would highly recommend not skipping ahead and understanding why a higher watcher count slows down an AngularJs application before getting your hands dirty with coding.

Day 3:

One-Time binding to the rescue! Perhaps the most valuable resource you will come across for reducing total AngularJS watchers is one-time binding. The basic theory behind one time binding is that for elements on your page that only need to be rendered once and will not be changed based on user interaction should use one-time binding. I would recommend using the following steps for applying one-time binding:

  1. Take stock of what your total watcher count is using the Angular Total Watchers Plugin recommended above – you did download the plugin right?
  2. Don’t be discouraged if the watcher count is incredibly high. We will reduce it to a manageable number before the end of this article
  3. Open up your html code and do a global find for the text “{{” and replace it with “{{::”. Refresh your page and observe total watcher count drop dramatically for your page like magic.
  4. Repeat step 3 but do a global search for ‘ ng-if=” ‘ and replace with ‘ ng-if=”:: ‘. Also follow this syntax for ng-show, ng-disabled, and basically any other ng-* directives.
  5. Your watcher count should be incredibly low at this point and your app’s look and feel should be exactly the same upon page load. But, unfortunately – all user interaction on the page will not function anymore.
  6. Go through all the places in your html code where you applied one time binding and determine whether the values of these directives will change based on user interaction with the page. If they only need to be displayed once to the user and will never change until page refresh, leave the one-time binding and move on. For the data elements that could change based on user interaction or new data – remove the one-time binding syntax :: off the html elements.
  7. Refresh your page after you have gone through this exercise and observe that your watcher count should be in a lot more managable state now.

NOTE:  One-time binding was only added to the AngularJS core in version 1.3. If you are using an earlier version than 1.3, please navigate over to the Bind-Once plugin and implement in your code which will give you the same functionality as the 1.3 and beyond :: syntax.

Day 4:

The next code optimization will discuss improving performance on the ng-repeat directives in your application. Look – if you have used AngularJS – you gotta love ng-repeat. It’s simple implementation and vast power is one of the best features of AngularJS. But, it certainly comes with it’s drawbacks when the total elements in the array it is being applied to starts to grow. Consider the code block below:

<ul ng-repeat=”beer in beers”> <li>{{beer.name}}</li> </ul>

If you have applied one-time binding to your element within the ng-repeat {{::beer}} and don’t have to worry about the elements in beers array changing, you are in great shape already. BUT, if you have a dynamic list of beers based on user interaction or filtering, you will notice your page will re-render extremely slowly based on user interaction. Assuming that you have a dynamic list that can be filtered or refreshed based on new data being retrieved from the server, you are likely to have code along the lines of $scope.beers = newBeersFromServer based on a user click. Even if the list of beers retrieved from the server has ONLY one more beer in the array, angular logic will go through and erase all the DOM elements on your UI and repaint them – an extremely expensive operation. How to improve this you ask? By using another awesome feature you have likely heard of called trackBy. (Note: trackBy was added to Angular core in 1.1.4 so the implementation below will not work for you if you are using an older version of AngularJS). A high level explanation for trackBy goes as follows: AngularJS appends a unique $$hashKey property to every element in your array so that ng-repeat can ensure that it is not rendering any element twice. Every-time your list is refreshed, ng-repeat will – unfortunately – go through your whole list and assign a new $$hashKey to every element in the list and repaint the entire DOM again. Instead of using the native $$hashKey to allow ng-repeat to keep track of your elements, AngularJS allows you to specify a unique property in each element in your array for AngularJS to use for rendering and tracking for changes and updates. Using this knowledge, we can update the code above to state:

<ul ng-repeat=”beer in beers track by beer.id”> <li>{{beer.name}}</li> </ul>

Here, we are assuming every beer in your list has a different ID and that ID for each beer stays the same in your database throughout it’s life. Now, whenever Angular re-renders your UI, it does need to dynamically repaint the DOM for every single beer in your list as it sees that prior to refresh and after refresh, the ID for most of the beers in your list are the same. AngularJs knows to only add the DOM elements for new beers which it was previously not tracking in the beers list – aka the new beers that were fetched from the server. Check out this fiddle to get a better understanding of the power of trackBy. By default, the code is not using trackBy to render the list of elements and is painfully slow. Once you enable trackBy – the refresh will be lightning fast as Angular will have no need to repaint the DOM. It really is THAT SIMPLE to improve a noticeable improvement in the re-rendering of your list inside the ng-repeat. Simple rule to follow: If you are using ng-repeat, you should use trackBy.

Day 5:

Today, we will discuss ng-if and the power it has to improve your application rendering dramatically. First, a quick primer on ng-if versus ng-show. Using ng-if is (almost always) recommended over ng-show. The reason is as follows: ng-if actually dynamically adds and removes DOM elements from your DOM whereas ng-show will just change the visibility from display: none to display: block. You can already imagine how using ng-if will greatly reduce the total number of watchers in your application, especially if you have multiple ng-ifs inside a ng-repeat. ng-if is easy enough to understand, but please follow the AngularJS core docs to get a better understanding of the directive. To watch the watcher count in your application drop considerably if you have a lot of ng-show directives, do a find all on “ng-show” and replace with “ng-if” and notice the total watcher count of your application drop using the watcher count plugin. Although implementing ng-if will already considerably improve your performance, another enhanced implementation of ng-if is using ng-if-start and ng-if-end(2 directives that were added to AngularJs as of version 1.2). Consider the code example below:

 {{beer.name}}

{{beer.strength}}

{{beer.countryOfOrigin}}

{{beer.totalHops}}

  • Instead of having to use multiple ng-ifs in your code, you could change the structure of your code to the following:

 {{beer.name}}

{{beer.totalHops}}

{{beer.countryOfOrigin}}

{{beer.strength}}

 

Above, you can notice – that you have the same ng-if statement applied to multiple elements in your DOM – angular will add a watcher to every single ng-if element. This would lead to the application having 3 watchers in the first code example above for each ng-if=”beer.ipa”. BUT, since you have multiple ng-ifs with the same check, you can restructure your code to use ng-if-start and ng-if-end to reduce your watcher count to 2, 1 for each of the ng-if-start and ng-if-end. While this doesn’t seem like a noticeable difference of watcher count reduction, any watcher count reduction within a ng-repeat can prove to be extremely valuable as the list of elements in your ng-repeat grows.

Summing Up:

Alas, we have come to the end of our journey and 5 day work week of improving the performance of our AngularJS application. The final step of the journey is to once again open up the AngularJS watcher count plugin and go through each of the pages on your application to observe total watcher count. As recommended by the AngularJS core team, each page in your application should have under 2000 watchers. Once we get above 2000 watchers, you will notice a considerable performance drop in your UI and your page will start crawling, especially on old phones and laptops. If your watcher count is already under 2k for each of the pages in your application, you are likely in good shape and can be confident in knowing your application is well optimized- but if you wish to optimize your application further I would recommend the resources below for improving your application performance even further. As you can see, a few simple changes can make a huge difference in performance when it comes to AngularJS. I hope the above tips help you in developing a responsive and speedy application.

11 tips to Improve AngularJS performance

Speed up AngularJS Performance with simple optimizations

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s