With the advent of Angular 2 and React, there is a lot of backlash against Angular on performance. While some of them are justified, a lot can be done to make Angular UI peppy. Having built many complex front-ends with Angular, I wanted to share a few things we learned in the trenches. Also, some of the advice out there on tuning Angular is outdated after the 1.3/4 release. So here is the latest on how to get the best Angular performance and tips to learning Angular faster!As with any performance exercise, don't start anything without profiling. Premature optimization is the root of all evil - Donald Knuth.The tools we found most useful are:1. Chrome profiler2. Timeline TabSee exactly what your application does in a given timeframe. Tracks resource loading, javascript parsing, style calculation, and repainting.
Angular performance tips:The chrome profiler output is very noisy. For Angular related timing, use the “Flame chart”. Here you can see digest cycle timings, ng-repeat bottlenecks etc. This is super useful.
Batarang (AngularJS browser plugin):This is from the Angular team that helps in profiling and debugging. Pay attention to the Models and Performance tab. But beware its a bit slow and doesn't handle large applications well.
ng-stat:This is a simple yet useful tool. Once you measure and understand your current Angular performance, plan your goals. When setting goals, instead of focussing just on raw times, do consider perceived performance. In UI, perceived performance is more important than actual performance. The perceived page performance is how long the user thinks the major elements of the page took to load. By definition it is highly subjective - some users may think that the page is loaded just because the initial furniture appears. But for most users, this will be the parts of the page they consider most important. Is it Angular, really? Before we blame Angular, let's make sure the problem is not elsewhere. Take care of non-Angular performance items before tackling Angular.For instance, fix your APIs. Implement all the YSlow principles.Now that you have taken care of all that, let's look at Angular-specific items. Before we begin a good understanding of how Angular implements 2-way binding via its dirty checking mechanism in the digest loop is imperative to understanding many of the performance strategies. If they are not already familiar with this, a quick primer here.Let us recall a couple of facts. Anytime a value changes, all watchers tied to the scope are evaluated. This is a multi-pass evaluation to account for values changed in the loop itself.So, the time taken by the digest loop has a direct impact on the performance. The number of watchers, time taken by the watch expression, and the scope in which the loop runs are important.Angular performance tips - step 1:Optimize watchers on the digest loop. Angular performance is greatly impacted by the number of watcher expressions and the complexity of expressions. The rule of thumb is, at around 2000 expressions a page begins to lag in performance.Newcomers to Angular are surprised by the number of watchers as they don’t realize the implicit watcher Angular adds. When a $watch is called on a scope value, or value is bound from the DOM with interpolation, ng-repeat, ng-switch, and ng-if, or any other DOM attribute/element, a function gets added to the $$watchers array of the innermost scope. All these watchers quickly add up.So here are a few things you can do to improve performance:
- Decrease the number of watchers by avoiding long lists with pagination or infinite scroll.
- Remove or disable watchers as soon as possible using the
unbind function.var unregisterFn = $scope.$watch(‘name’, function(){/*expr*/}); unregisterFn();
- Keep the watcher functions simple. Remember that these functions are evaluated in every run.Don’t observe function results directly.
- Call back functions attached to watchers should not change its own or other watch values. This is to avoid the digest cycle running again. The cycle runs till all values are returned not dirty in a single run.
Angular performance tips - step 2
Bind once when possible. From Angular 1.3, this feature is available. For cases where the values are static, once bound to the UI, we can use this. Angular will bind once and then remove the watcher. Instead ofHello {{name}}!</p>do<p>
Hello {{::name}}!</p>
We can use this in any type of Angular expression like ng-repeat.
Angular performance tips - step 3:
Beware of $apply.Usually, when a value changes, only the watch list associated with that scope will be evaluated. But if we use $apply, the digest cycle starts with the root scope. You do need $apply while changing values outside of Angular context and want to bring it back. Even here, if your use case needs changes to be propagated only in the current scope and downward use the local events facility provided by https://github.com/ansukla/ng-perf Using this you can trigger $digest instead of $apply.This is very useful when you are building UI components like auto-complete where the event needs to be applied only to the current directive.
Angular performance tips - step 4:
Debounce ng-model.This is available since Angular 1.3.With this we can tell Angular to wait for a certain amount of time and when the current operation is in progress. For example, in type-ahead fields instead of triggering the digest cycle for every keystroke, we can delay it by 500 ms.<<div ng-controller=”UserController”<>
<<form name=”userForm”<> Name: <<input type=”text” name=”userName” ng-model=”user.name” ng-model-options=”{ debounce: 500 }” </>
<<button ng-click=”userForm.userName.$rollbackViewValue(); user.name=””<> Clear <</button><<br </>
<</form><<pre>user.name =<</pre>
<</div>
ngModelOptions
Angular performance tips - step 5:
Disable debug info in production. Since 1.3, we can use the following snippet to significantly boost the performance of load time by disabling debug information.config([‘$routeProvider’, ‘$compileProvider’, function($routeProvider, $compileProvider) { //configure routeProvider as usual $compileProvider.debugInfoEnabled(false); }]
Angular performance tips - step 6:
Avoid DOM filtersAvoid DOM filters that are slow, instead, pre-process data. Angular provides the $filter with which we can run filters in Javascript before passing it to the DOM. This will pre-process the data before binding it to the view which avoids a step of passing the DOM.Instead of{{ filter_expression | filter : expression : comparator }}do$filter('filter')(array, expression, comparator);
Angular performance tips - step 7:
ng-show instead of ng-ifBoth Usually and ng-if hide an element from view if a given condition is true. But they do this in a very different fashion.Ng-show/ng-hide do this by using the display: name CSS style. Ng-if actually removes the element if the condition is false and adds it back when the condition is true. The implication of this is all the watchers associated with this element are also removed from the watch list.So use ng-show if
- if you hide/show the element frequently
- if your initialization logic is heavy Use ng-if
- hide/show rarely
- a lot of watchers associated with this element
Angular performance tips - step 8:
Avoid frequent reflows/repaints / parseHTMLUsually the browser applies to style and layout changes in batches since DOM operations are expensive. Lagging is caused if by constant repainting, which appears when batching is not possible.Possible solutions: Write inline templates in your directive’s template propertyPrefill angular’s build-in $templateCache with your templates. When you require an external template, AngularJS first checks the cache. There’s also a handy grunt task that automatically minifies and combines your HTML templates and prefills the AngularJS $templateCache.Grunt angular templates
Angular performance tips - step 9:
$watchCollection instead of $watch where applicableAvoid deep checking with$watch('value', function(){}, true).Use$watchCollection('value', function(){})if you need only the first layer of the object’s properties to be checked.Reference: code.angularjs.org
Angular performance tips - step 10:
Keep the size of the object returned from server small.It is appealing from the design perspective to return JSON representation of an entire model object to a view. But for better performance consider creating custom serializers to return a partial representation of a model.For instance, in an event management system that we developed, we need to display a bunch of events on a page. Each event displayed needs a few attributes from the user model. We retrieve only these attributes instead of the whole user object with an annotation like this.@JsonIgnoreProperties({ "username", "email", "phonePrefix", "phoneNumber", "language", "address", "enabled", "verified", "emailVerified", "phoneVerified", "timeZone", "gender", "agreedPrivacyTC", "hasReadPrivacyTC", "phoneNos", "emailIds", "fullTimeZone", "fullCountryName", "loginCount", "devicePhoneNumber", "phoneVerifiedBy", "emailVerifiedBy", "haveVerifyCode", "enableTravelPlan", "noOfContacts", "noOfRegContacts", "noOfChats", "noOfEvents", "noOfEventsInvite", "languageName", "dateFormat", "aliasPhoneNumber", "conflict", "conflictMessage", "accountVerifiedOn", "isMobileUser", "phoneConflict", "createdOn", "modifiedOn", "dateOfBirth", "deviceType", "lastLoginTime", "version", "emailVerifiedOn", "phoneVerifiedOn" }) public User getUser() { return user; }
Angular performance tips - step 11
Go outside AngularNot everything needs to be Angular. For things that don't work well in the Angular context like heavy DOM manipulation, consider using Javascript using a directives link function. Hope this helps.Do share your bag of tips and tricks in the comments section. About us: Ideas2IT is a high-end Product Development firm with a strong front-end development practice. Angular, React, Ionic, Knockout, whatever your choice, we can help you develop, test, and performance-tune your application front-end. Let us know if we can help.