I am starting with this as I feel that this is the first place where any angular expert would suggest you look at. Now, if your angular page is slow and laggy, then either you are using too many watchers or you are making them do too much work.
Let me give you a summary on how watchers work. Angular uses dirty checking to keep track of all the changes in the application. What this means is that it will have to go through every watcher to check if they need to be updated (basically angular restarts the digest cycle). If one of the watcher relies on another watcher, your angular would have to restart all the digest cycles to make sure that all of the changes are accounted for and it will continue to do so till everything is stable.
There is always a workaround but first, you need to identify your application’s watchers. If only one screen is slow then most probably that screen has a lot of bad scopes, too many watchers or handlers that are taking too long. If everything is slow, then I have bad news for you, your digest loop is taking too long and you are most probably using angular wrong.
One useful tool for identifying watchers is Batarang (I highly recommend it). You can also try timing your digest cycle.
Make sure that your watches are not taking too long, either try segregating the functions which are used or minimize the number of events that are being called.
Also try to avoid using the deep watch. But what if your deep watches are required because your business logic demands it?
Well, there is a solution where you can switch deep watch to watchCollection. How?
Well, you’ll have to change your watches to watchcollection, perhaps something like:
Everybody uses this but nobody ever looks at all the edge cases. We usually tend to work with 10-15 items but nobody ever tests with 1000 items. There might be cases where using so many items might start breaking your application.
The simplest workaround is to use infinite scrolling or pagination but if you want some alternate solution, try looking at the timeline of your page and there may be a slight possibility that you are using some widget or something like
ui-select which would be taking too much time to render.
Also, try using track by
$index wherever possible. Why?? Well, let me give a real-world use-case. Let’s say you have a refresh button which basically fetches new items for you, something like:
Now whenever we are going to hit the refresh button and fill the
$scope.someList variable with some data, what ng-repeat would be doing is removing all the
li elements of list and creating them again. Why does ng-repeat do this? Behind the scenes
ng-repeat is adding a property
$$hashkey to each item so as to keep track of it. Now even if we are updating
$scope.someList with the same data,
ng-repeat will still trigger all the DOM manipulations again because your
$$hashkey will never be the same. There are a few hacks but the best solution to solve this problem is
track by. It allows you to specify your own key for
ng-repeat to identify objects by, instead of just generating unique IDs. So if you are getting the same elements then
ng-repeat will not recreate all the DOM elements again and again.
This is perhaps the most easiest thing to fix for improving your page’s performance. What do we do? Use
ng-if instead of
ng-hide. Why would this help? The reason is quite simple,
ng-show is going to render the element (where you have added
ng-show) and use
display:none to hide it. What does
ng-if will actually remove the element from your DOM and will re-create it if it is required. Do note that, you might want to take a hard look at your use-case and see if
ng-if is beneficial. You may need something like a toggle (
ng-show) which toggles the elements on and off often.
4. DOM access:
For those who are fond of accessing the DOM using
5. Objects and Arrays:
I believe I read somewhere that arrays are always faster than objects. Well, that is true if you are iterating them. But if you are using the iteration to get a particular element then we should probably have a look at that silly thing which we learnt in our college days. Remember? Objects or dictionaries have a complexity of
O(1), which is any day better than
O(n)(for arrays), so we can use it as a key-value pair and get a 10x optimization just like that. This whole optimization depends on the developer and how they want their data to be represented.
So you remember that
ng-repeat which we talked about above well it has a cousin :
filter, which might be slowing your application down. How? Well,
filters run twice per digest cycle, once when anything changes, and another time to collect further changes. They do not actually remove any part of the collection from memory either, instead simply masking filtered items with css. Even I wasn’t too certain about this but upon further investigation I found that this actually happens and might literally break your application if you are not careful enough.
This is probably the latest love of mine. When optimizing your angular, one must always try to debounce. What is this debounce I speak of? Well, debounce is a function which limits the number of events which are getting triggered. Example, let’s say I have some sections and a sidebar with the list of sections. Now everytime I scroll to a section my sidebar should highlight the section and remove the previously selected section. Hmm, lets say I use
$(window).on to trigger an event everytime I scroll (bare with me, this is just an example , I know there are better ways, but we want it to be dynamic). So my function (
$window.on ) should calculate the section height, remove the previously selected sidebar element , highlight the correct section which the user is at and this will be triggered everytime the user is doing a scroll.
This would work for a few sections, but what happens when I have 100 sections and 100 section items in my sidebar? I ‘ll tell you. This very function hits your application in the stomach and makes your application super laggy.
How do we solve this problem? DEBOUNCE IT!! Basically, what your debounce would do is make sure that everytime you scroll , you are not calculating the value, but waiting for some interval and then firing the events.
How?? Well you can wrap your function on something like this:
What this would do is fire the events of the function, on which you are wrapping debounce, at a certain rate and not everytime. You want to know the beauty of debounce, you can use debounce on ng-models or anything that is getting fired like crazy. This usually gives a huge performance boost. Psst.. React guys , you can use this logic as well.
8. Digest cycle:
Sometimes, while using angular, you have to explicitly tell your Angular to initiate its digest cycle as you may require to do some dirty checking. This is a pretty common use-case in angular and most developers would perhaps do this on some service or some directive. Most of the time this is done by using
$apply. This might not be the best approach here. Why?? There might be a case where you are triggering a
$apply() and a digest cycle is already in the process. This condition leads to the infamous
rootscope:inprog errors. Most developers try to fix this by using
intervals or if conditions on the phase of the scope to check whether a digest cycle is already in progress or not. But there is a better and a more optimal solution
scope.$evalAsync. What does it do? Well, it lets you queue operations up for execution at the end of the current digest cycle without marking the scope dirty for another digest cycle. This is much more faster than any interval.