You are viewing our old blog site. For latest posts, please visit us at the new space. Follow our publication there to stay updated with tech articles, tutorials, events & more.

Optimizing an AngularJs app

0.00 avg. rating (0% score) - 0 votes

AngularJS may have a lot of advantages over other frameworks for building front end applications but it can have performance problems when an app starts to get large.

There can be several reasons an AngularJS application may be slowing down. Recently during our internal project which was built in angular, we faced a lot of performance issues as the application grew in size and complexity. We took many steps to optimize our application and make it execute without any performance lag. Some of the approaches are discussed below.

Reduce Watchers

We had lot of 2 way bindings in our project and we binded every model both ways in our view. But as the app grew in size the $$watcher population increased significantly and started hampering app performance. Angular uses dirty checking approach to keep track of the changes in models and to reflect them in the view (bi-directional data binding). This means it will have to go through every $$watcher to check if they need to be updated (call the $digest cycle).

You should be mindful that the number of bi-directional data bindings should not exceed more than 2000 data bindings on the page for each $digest loop.

One-time binding syntax {{ ::value }}

Angular Js 1.3 came up with a feature which is the ability to bind data to the view without adding a watcher. This feature greatly helped us to reduce watchers. We used this extensively on all those variables which needed to be rendered in the view only once.one way binding

In the above code first < p> tag will not get affected on clicking the button as it one time binded using ‘::’, whereas the second <p> tag will be updated every time the button is clicked.

Angular sets $$watchers on below mentioned points and we should use them wisely

  • $scope.$watch
  • {{ }} type bindings
  • Most directives (i.e. ng-show)
  • Scope variables scope: { bar: ‘=’}
  • Filters {{ value | myFilter }}
  • ng-repeat

$$watchers (digest cycle) run on

  • User action (ng-clicketc). Most built in directives will call $scope.$apply upon completion which triggers the digest cycle.
  • ng-change
  • ng-model
  • $httpevents (so all ajax calls)
  • $qpromises resolved
  • $timeout
  • $interval
  • Manual call to $scope.$applyand $scope.$digest

 

ng-repeat

As we had many lists in our application, we used ng-repeat extensively with deep nested objects as well. But the page started to lag after the array of the list started to get complex.

ng-repeat directive is one of the biggest sources of delay in Angular, it is a powerful directive of angular and often easy to abuse and misuse. Handling ng-repeat inefficiently will create too many $$watchers.ng-repeatSuppose if the length of above array is 10, then this ngRepeat creates 10*2 + 1 $watches in angularJs. Such a simple ng-repeat causes 21 watchers, and if the array and bindings are too complex then it will add significantly to the watcher population of the page.

We took some steps to efficiently use ng-repeat which were very fruitful.

ng-repeat track by

ng-repeat has an attribute “track by” with which we can supply a unique id. This reduces the number of dom repaints and dirty checking needed.track byWhen we add track by we basically tell angular to generate a single DOM element per data object in the given collection. This could be useful when paging, filtering or updating the collection from an api call, or any case where objects are added or removed from ng-repeat list. Since in the above example id would be same for an object in the current collection and the one fetched from the server or filtered locally, so angular will not repaint the dom for the same id. We can also have duplicate elements in the collection by using track by, which is not possible with the default tracking function of ng-repeat.

ng-repeat filter

Avoid using ng-repeat filter, it is better to pass a filtered array to ng-repeat than using filter.ng repeeat filterGood to filter collection from javascript rather using filter inline in above code.

ng-repeat function

Avoid using function to return the array to be used for ng-repeat. These functions need to be recalculated even when there is no change, unnecessarily reloading the ng-repeat each time.ng repeat func

$watchCollection instead of $watch

Angular supports a third parameter in $watch method which allows deep checking the object, which means to check every property of the object, which can be quite expensive.

$watch(‘value’, function(){}, true)

To address this performance issue we used $watchCollection which works same as $watch with 3 parameters, except only that it checks only the first layer of the object properties.

$watchCollection(‘value’, function(){})

Debouncing ng-model

We had some filters, validations and api calls on the input fields which were called on every key press and it triggered the digest cycle causing Angular to update all $$watchers and bindings in the app to see if anything has changed. We used debounce option on ng-model which helped us to greatly reduce the server load, filter calls and eventually the digest loop calls.

Debouncing is a method to apply changes to the model when a user stops typing for a specified amount of time. This method can significantly improve the user experience as the user will not experience lag due to $digest loop while typing.debounceThe value in the span will get updated only if no changes happens to the input field after 1000ms.

ng-if instead of ng-show

ng-show and ng-hide uses css ‘display’ property to show hide elements which means the node is always present in the dom and is a part of calculations done in every $digest cycle. This is only useful if we are toggling a particular node very often. For all the other cases we used ng-if can as it removes the node from the dom, which reduces unnecessary calculations.

Optimizing filters

Initially in our app we used many filters inline in the view. But as the number of filters increased the number of $digest cycles calls also increased as every filter in the view is called a minimum of two times per $digest cycle. So we moved all filters in our javascript which were required to be rendered in the view only once. This pre-processes our data before sending it to the view, which avoids the step of parsing the DOM.

That is to say that, rather than using the currency filter in the view, we can apply the filter to the model when we retrieve the data in the controller or service.

That is to say instead of using the filter in the view, like so:optimizing 1We can instead use $filter service:optimizing 2AngularJs is a powerful frontend framework but it has its own limitations. It is important to understand what causes an AngularJS application to slow down, and to be aware of trade-offs that are made in the development process.