FrontendWalla

Solutions for Real World Frontend Problems.

How to Improve the Performance of Angular Applications?

Angular is a popular SPA Framework and I just love it. But if you are a beginner at this you will often hear about other frameworks available in the market (React, Vue), etc, and compare these frameworks.

One of the parameters that often is used for comparison is the Performance of these frameworks. After having worked on various Frameworks and Applications my opinion is – Performance is not entirely a framework thing and depends upon a lot of factors. In this article, we will discuss How we can create a High Performing Angular Application by taking care of a few things. In my previous posts, I have discussed general articles on performance improvements in the frontend by optimizing images, CSS, JS, Fonts, and resource delivery these concepts apply here as well and we will not discuss them here and concentrate just on the performance optimizations from Framework Perspective.

Change Detection Strategy for Angular Performance

Let’s first understand what is change detection and Why it is important in Angular and then we will discuss how we should tweak change detection to improve the Angular Application. Change Detection is an Angular way of keeping sync between data and UI. So in simple terms as soon as a data change happens how UI reflects that change is taken care of by Change Detection. Change detection is triggered when a Javascript Event (Click, MouseEnter, etc) Happens, or when other browser APIs like setTimeout, setInterval Ajax calls, etc are triggered.

When Change Detection has triggered the values in a template expression are compared to its previous values by default. These Template bound values maybe come from data shared across various components hence the entire component tree is checked to make sure the most up-to-date state is reflected on the DOM. This is usually ok but when we have a complex and large component Tree this can become a performance problem.

Angular allows us a way to switch this change detection strategy from default to onPush in which case a component change detection is skipped. if you are confident that switching some components to onPush will not affect the state of the application you can use this strategy. So optimizing Change Detection on the Angular App can give us better runtime performance. We will discuss Change Detection in Detail in a Separate Article as well since after applying onPush you may observe that lot of items that were automatically updated in your view have stopped updating so a careful approach to using Change Detection Should be used.

Using TrackBy on ngFor Loops

When we loop on a list of items in the template using the ngFor directive we render the entire list. This is ok for small lists and lists that don’t frequently update. For Large Lists that Frequently update angular by default render the entire list in ngFor. Imagine there are thousands of items and only one item is updated. This is a negative impact on performance. Angular provides something called trackBy which modifies the ngFor directive by adding a tractByMethod which helps do the comparison on how to identify the changed items.

// in the template file mycomponent.component.js
<div *ngFor='let item of items;trackBy=trackByFn>
{{item.name}}
</div>

// in the component ts file add this.
trackByFn(index, item) {
    return index;
}

Unsubscribe from Observables

Angular uses a lot of Reactive Programming approaches, especially in HTTP Requests. When we subscribe to an Observable in Component the subscription waits for the updates and whenever an update is available takes necessary action like updating the UI with the latest data etc. But when we leave this component and reach another component the subscription is still alive. This can lead to Memory leaks and can slow down the runtime performance of angular applications badly. So the best idea is to use onDestroy lifecycle hook of a component and unsubscribe from the Subscriptions.

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription: Subscription 
    ngOnInit () {
        var observable = Rx.Observable.interval(1000);
        this.subscription = observable.subscribe(x => console.log(x));
    }   
    ngOnDestroy() {
        this.subscription.unsubscribe()
    }
}

Third-Party Packages

To enhance the capability of your application we include a lot of external packages in our Angular Application. There are so many packages available for similar functionality written by different developers. So we must use these third-party packages wisely. We should always ask do I really need this package or if the desired behavior can be easily coded by myself. If we really need a third-party library we should check the number of downloads, when was it released, the Last download any comments, and also support with the current angular version, etc.

Avoid Computation in template Files

Templates are the place that should show your data. Although in the template expression, we can do computations as well this should be completely avoided. For any data manipulation that we want to do in a template, it’s better to use pure pipes. Look at the list of available inbuilt pipes in angular and if there is a need for custom logic create custom pure pipes.

Code Splitting Through Routes

My First application in Angular 2 was a huge enterprise application and we were learning angular at that time so we coded everything under one default App Module. The size of the main module was big and it took almost a minute for the initial load. We later learned about Code Spit through Routes which involved creating multiple Modules and Modifying Route modules to Lazy Load some of these modules. This decreased the size of the main module and it loaded fast enough. The other modules were lazy-loaded based on the route configuration.

{
  path: 'admin',
  loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}

in the code above for Route admin instead of loading a component with Admin features. We have created another module altogether called the admin Module. This module is then dynamically imported and will load the default component attached to that module.

Route Preloading Strategy

After creating smaller module code chunks in the previous approach we realized that our initial module loads faster and this was a success story. But we saw that our navigation to subsequent routes took some initial delay. For this in the beginning, we used spinners to let users know that something is downloading.

Later we learned about route preloading Strategy which is a configuration provided by Angular Router it has two types PreLoadAllModules and QuickLinkStrategy. And as the name says PreLoadAllModules will Pre Load All the Lazy Loaded Modules in the Background whereas the QuickLinkStrategy will look at the Links Available on the page and only load these modules. The QuickLink Strategy needs an ngx-quicklink package.

Defining a Performance Budget

To ensure that the performance of your application does not degrees over time you can define the performance budget of any application. This Online Calculator can help us define the size of HTML, CSS, Font, Images, and Javascript that would be required to achieve a particular Time to Interactive. In our angular application, we can define this budget under the angular.json file.

"budgets": [{
  "type": "bundle",
  "name": "main",
  "maximumWarning": "190kb",
  "maximumError": "280kb"
}]

Here we are telling angular that the js bundle main should give a warning on 190Kb and an error on 280Kb. Now let’s say you modify your code later and the bundle size increases you will get this warning and error. This can also be embedded in your pipeline to stop your build to production in case the bundle size exceeds.

The Curious Case of Large Lists

This problem we recently faced in one of our angular applications, Where we had to show a large number of items (Hundreds of Thousands) as a list and it had text and image both per item while rendering this app and on normal scrolling, the Chrome Browser gave “Aw, Snap” and the application broke. What we understood is we are using a lot of Memory and since so many items are rendered on the UI The Browser is not able to handle it. Now there are some solutions to fix it. We used Virtual Scroll for fixing it. The Virtual Scroll solution basically renders only those items which are visible on the screen.

Component Dev Kit provides the ability to implement it. First, install the library

npm install --save @angular/cdk

Then import the scrolling module

import {ScrollingModule} from '@angular/cdk/scrolling';

...
imports: [
  ScrollingModule
...
]
...

Then finally create the viewport.

@Component({
  template: `<cdk-virtual-scroll-viewport itemSize="18" style="height:80vh">
    <div *ngFor="let item of list">{{item}}</div>
    </cdk-virtual-scroll-viewport>`
})
export class ScrollComponent {
  list = Array.from({length: 100000}).map((_, i) => i);
}

A Word on “AOT”

The ahead-of-Time compilation is a better way of building Angular apps and after Angular 9 “AOT” is the default build choice before Angular 9. The default build was JIT which postponed the compilation process at runtime which delayed the load time and performance of the application. So if you are on the latest angular version you are good. In older version you need to pass –aot flag.

Run Outside Angular

We learned about Zone Js in change detection section and we now know that trigggering un neccessary change detection cycles can lead to angular performance Problems. There are some case in our application where we use setTimeout or requestAnimationFrame or call a external Third Party (Graphs Maybe) which doesnt have any link with Angular. But when their code is inside Angular Components. NgZone attached its behavior to them and therefore we see change etection triggered on them as well. Zone provides a faciltiy to run somehing outside angular and thus its detached from Change Detection.

this.ngZone.runOutsideAngular(() => {
    this.interval = window.setInterval(() => {
        this.setNextColor()
        this.paint();
    }, 10)
});

Conclusion

If we take care of these certain aspects in Angular we can really achieve an Angular Application with Lightening Speed. The Suggestions above might not be required for small applications and application that deal wth small amount of data. But when we have Application with Large number of Components or Application that deals with Huge Data Sets to Show lists of items we may have to refer to this list and do run a performance check to see if we are ready for releasing to the User.

How to Improve the Performance of Angular Applications?

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top