7 Tips to Optimize Your Angular App/Application

Pankaj Parkar  Print   20 min read  
01 Mar 2019
09 Mar 2019
Advanced
748

Angular is by default provide superfast Performance, but when it comes to designing complex and realistic application, you will start seeing cracks inside your application performance. The reason behind performance degrade can be

  • The team haven’t followed best practices

  • The solution is following best practices, but there is a need for optimization.

    The 1 st case can be easily resolved by enforcing best practices and style-guide in a current code base. For the 2nd case, you can optimize the current codebase by applying some ways to tackle performance threatening. So, here we’re going to look 7 performance tips

  • Controlling Change Detection

  • Lazy Loading

  • AOT compilation mode + compression

  • Use Pure Pipes

  • Use trackBy function on ngFor

  • Don’t try to use Angular for everything

  • Service Worker and Server-side rendering

1. Controlling Change Detection

Angular uses unidirectional data flow when it comes to updating bindings on a page. Before getting into this, we should be aware of “what is change detection?” Well, Change Detection is a process by which Angular updates respective model values on view (HTML). Let’s dig a little deeper into change detection. If you’re familiar with AngularJS (1.x), this process was known as a digest cycle, it's not exactly similar to Change Detection, but the philosophy remains the same.

How change detection works

The angular application is consisting of a tree of components, each component has its own `ChangeDetectorRef` provider, which is responsible for handling binding update local to that component. When application bootstrapped, it initially creates `ApplicationContext`(root) and fires `tick` method on root component, which starts running change detection on each component from top of the bottom of the tree.

“`tick` method is responsible for running change detection (detect changes method), on each component.” Basically, there are a couple of change detection strategies,

  1. Default (default strategy for a component, if not set)

  2. OnPush

“Default” Change Detection Strategy

Generally, when change detection fires at any component level, it starts triggers from the root component. It evaluates all the bindings of a component, irrespective of passed `@Input` binding has been changed or not. In a real application, this kind of change detection behavior might lead to performance impact. Assume a page where there is 100th level for a component tree and some intermediate level component triggered change detection, which eventually triggers all components binding evaluation from top to bottom. That means even though we know that we shouldn’t be running change detection for all other components, we are unintentionally running change detection for every component.

(change detection – default strategy)

In the above diagram, you can see that on B component it ran change detection, after that it will run a `tick` method that eventually runs change detection from root component to the leaf components.

A component changes the value of B components `Input` binding. Our all components on default strategy, it runs change detection over all components like shown in the above diagram. So what you can see is, even though we were highly interested to run the change detection on B’s component tree, it's happening all over. Fortunately, this nature can be easily tackled by toggling change detection strategy OnPush.

“OnPush” Change Detection Strategy

When you set the change detection strategy to `OnPush`, it makes our component smarter. It calls the current component change detection method only when the incoming `@Input` bindings value would change. In other words, we can say, it runs change detection for descendant components only if Input binding are changed. This helps to save much unnecessary change detection iteration, at the end this improves the performance of your application.

(change detection – onpush strategy)

In the above diagram you can see component B and C are set to OnPush strategy, so once change detection happens on component B, it triggers from the root of the tree to bottom. And root component changes `Input` property of component `B`

As shown in the above diagram, it will only run change detection for component B and its descendant, since its Input property got to change. And change detection on component C doesn’t fire. Great! This is what we wanted do. “One of the major performance wins would be, try to run change detection cycles as less as you can. Even removing ZoneJS from the application and running change detection manually could improve performance significantly”

TL;DR

Now question is

  • these two flavours are enough to handle on uses cases?

  • Are there any different change detection flavours exists?

Okay! Consider you have a big application that has a real-time widget which is constantly pooling data (each 300ms) from a server like chat widget, live score, stocks value, etc. In such cases, if you keep component in Default / OnPush strategy, you will be having multiple change detection cycle happening on each data pool operation. And if you have this kind of widget in an extensively large application, your other components will run their CD even though they are not necessary. To avoid such cases, you can easily use this technique to detach the component when you don’t want to pay a cost in terms of performance. And then you’ve to take care of running change detection manually by calling `markForCheck` method on the current component. Oh! What is `detach` and `markForCheck`? Don’t worry we will look at them one by one.

How to detach component from change detector tree

If you look at ChangeDetectorRef API, there are several methods available, those can also be utilized to boost performance, methods namely are detached, markForCheck, retach, etc. For setting this you have to inject `ChangeDetectorRef` dependency on the component constructor and then call `detach` method on `changeDetectorRef` object to take out the component from change detector tree. This line will pluck out a current component from the change detection tree. What that means is, running change detection inside application wouldn’t run change detection to this component and its descendant.

 export class AppComponent { 
 constructor(cd: ChangeDetectorRef) { 
 cd.detach() 
 } 
}
 

And whenever you want to fire change detection on a detached component, you have to call the `markForCheck` method on that component which will eventually mark that component path from root(top) component to detached component, and update bindings only for next change detection run.

mySpecificMethod() { 
// awesome logic here 
// once done with the call, markForCheck method which will run CD for next run only 
this.cd.markForCheck();
}

(calling a markForCheck method to run change detection on detach tree)

(detach component from change detector tree)

(detach component from change detector tree)

In the above diagram you can see that C component has been set detach from the component tree, basically, that does pluck out that component (B) tree with its descendant separately. No change detection will be running implicitly on this C component and its descendants.

(calling a markForCheck method on component C)

So when it comes to running change detection on the plucked tree, consider explicitly call `markForCheck` method, that will traverse tree up and mark the path till root component. Over here it has been marked as “component C to component B”. Thereafter it runs the change detection cycle over the recorded path.

TL;DR + TL;DR

Although there is one more method exists on `ChangeDetectorRef` provider, which is `reattach`, it is responsible for bringing back your plucked component tree to its original state. For the same, you can call `reattach` method on `changeDetectorRef` instance. You can refer below code snippet for using the same

 mySpecificMethod() { 
// awesome logic here 
// rettach component back to tree 
this.cd.reattach(); 
}
 

In the above diagram, you can see that component C calls reattach method and then it brings back the plucked component tree in the change detector tree. What that means is, the upcoming change detection will be running on component C and its descendants.

2. Lazy Loading

While crafting an application, we divide major functionalities into a set of chunks, and those chunks are known as modules or sub-modules. Each module provides a unique feature. These features are pluggable and good to maintain. But when we load Large Application consist of 10+ modules at a single go. Eventually, code volume gets increase, effectively the final app bundle size grows tremendously. Loading large JS files on the browser can affect the initial boot time, which is not good. Ideally, an application should load faster. An ideal time is 3 seconds.

const routes: Routes = [ 
 {path: 'dashboard', component: 'DashboardModule'}, 
 {path: 'admin', component: 'AdminModule'}, 
 {path: 'purchase', component: 'PurchaseModule'}, 
 {path: '**', redirectTo: 'dashboard'} 
] 
 
@NgModule({ 
imports: [RouterModule.forRoot(routes)], 
exports: [RouterModule] }) 
export class AppRoutingModule { 
}

Don’t you see something wrong by looking at the above code? We are loading all modules and components on initial page load, even though there is only a single module functional on the screen. That means on initial page load Dashboard, Admin and Purchase module is going to be loaded. Think! Can’t we make a system better? We can think of initially loading those modules which are functional on that particular screen. Yes! You’re absolutely right. This feature is already applicable in Router API. It allows you to load modules on demand and lazily based on the route. This will show you tremendous performance gain in application boot up speed, as a web pack split bundles for each application route. Let’s refactor previous code

 const routes: Routes = [ 
 {path: 'dashboard', loadChildren: './admin#DashboardModule'}, 
 {path: 'admin', loadChildren: './admin#AdminModule'}, 
 {path: 'purchase', loadChildren: './admin#PurchaseModule'}, 
 {path: '**', redirectTo: 'dashboard'} 
] 
 
@NgModule({ 
imports: [RouterModule.forRoot(routes)], 
exports: [RouterModule] }) 
export class AppRoutingModule { 
}

Did you see something different in the above code? Yes, `loadChildren` option on a route. It's an option where you can mention particular angular `NgModule` path followed by `#ModuleName`. This helps to load the relevant bundle of an application based on the matched route. By having this thing in a place, we ensure that we will be paying the cost for what we’re seeing on the screen.

3. AOT compilation mode + compression

Angular can be run in two different phases

  1. Just in Time (JIT) Compilation Mode

  2. Ahead of Time (AOT) Compilation Mode

When application build is created in JIT Compilation mode, it only transpile TS file to JS. These transpiled files are sent to the browser. Thereafter browser process these files further with the help of Angular Interpreter (i.e. @angular/compiler) API. That eventually convert angular files `*.*.ngfactory.js` file. Also, it collates the component and HTML code inside JavaScript function and later those files being used by a browser to run Angular application. The thorough conversion process is taken care by `@angular/compiler` module on the browser, that’s why you have to ship `@angular/compiler` module to browser in JIT mode, which size is somewhere around 1MB approx. In total when you run an application in JIT mode you will see below factors which affect initial boot up time

(Total time is taken by JIT mode)

In the above diagram, you can see that the application do a lot of things behind the scenes before it starts. This reasonably degrades the performance in terms of initial page load. In JIT mode we require to ship @angular/compiler package which is huge (approx. 1mb). As the application gets bigger you start seeing more impact on application boot up time. You must be thinking about the alternative approach to this. Yes! there is. There is another way exist called AOT (Ahead of Time compilation)

AOT – Ahead of Time Compilation

Let’s see what AOT means, AOT stands for Ahead of Time compilation mode. In this phase angular compiler generate the ngfactory files at the time of creating a build package, rather than doing this on browse, it does it ahead of time. If we compare this with JIT mode, we can save several seconds and will eventually not shipping `@angular/compiler` to the browser. Ultimately that will improve initial load time. (You can’t use AOT mode if you’re loading your components dynamically, you have to use JITCompiler in that case). If you want to see AOT version of files locally, do run `ngc` command in the command line, that will generate *.ngfactory.js files.

4. Use Pure Pipes

Angular has something called Pipe. It is used for display model value on UI in a slightly different format. Eg. `date | shortdate` here shortdate filter will convert date object to shorter date format like `dd/MM/yyyy`. You can also use Pipe to slice and dice data for display purpose Pipes are can be categories into two parts

  1. Impure Pipe

  2. Pure Pipe

In short, the difference between pure and impure pipe is Impure pipe can produce different output for similar input over time, whereas pure pipe means a function that can produce similar output for the same input. Angular has provided certain inbuilt pipes, all of them are pure in nature. Basically, when it comes to binding evaluation, angular each time evaluate an expression and thereafter applies the pipe over it (if exists). It caches the value for that specific binding. Similar to that of memorization. Later if the same binding value tries to evaluate, angular fetch that value from the binding level cache. This out of the box memoization is done by angular for pure pipes. It’s recommended to use pure pipes for performance reason. Pure pipe only helps in the same expression evaluation consecutive amount of time. But in enterprise application we don’t have a simple use case, we need more than this. So, you can implement the memorization of your own. Two get more performance benefit, that means once a value is visited, those values are cached and next time if the same value arrives then the cached value will be return.

import { Pipe, PipeTransform } from '@angular/core'; 
import { DecimalPipe } from '@angular/common'; 
import { UtilityService } from '../services/utility.service'; 

let cache = {}; // caching variable 
@Pipe({ name: 'calculateAmout' }) 
export class CalculateAmoutPipe implements PipeTransform { 
constructor(private utilityService: UtilityService, private decimalPipe: DecimalPipe){
} 
/* Calculate and save the value into the cache, and return value from cache.
It could save some iteration of calculation by memoization */ 
private calculateMemoize = (val) => { 
if(!cache[val]) {
cache[val] = this.numberFormat(val); 
return cache[val]; 
} 
private numberFormat = (value) =>{
 var result = this.utilityService.calculateBalance(value); 
 return this.decimalPipe.transform(result, '.0-0') 
}; 

transform(value: any, args?: any): any { 
 return this.calculateMemoize(value); 
 }
}

5. Use Trackby on ngFor

I assume that you must be familiar with `*ngFor` directive, it helps to repeat the same template for each item of the collection. Consider a scenario where you’re retrieving a list of employees and displaying all employee’s data on the page. From that page, if a user adds a new employee from modal, we re-fetch the list from the server and reassign the value to list variable. That causes a re-rendering of that `ngFor` directive. Most of the times we only except to list get appended with newly added element when a new collection is re-fetched. It seems like `ngFor `unnecessarily re-rendering the DOM. This could be an expensive process when rendering a large collection on the page.

NOTE

Add and removal of DOM are one of the most expensive operations on the browser, actually those are not that expensive, but eventually, it causes the layout to redraw and repaint, which is more expensive.

Ideally, for such cases, we should not have to re-render the whole list, we should just append the newly added element in the DOM tree. That’s it. So you must be thinking how can we achieve this? Simple, you can use `ngForTrackBy` option which will help us to make `*ngFor` directive rendering smarter. `ngForTrackBy` accepts a function that returns the identity to compare with. So, what happens behind the scenes is, while rendering template for each iteration, it stores value to default value against each template, so next time before rendering the template it checks for the ngForTrackBy function value, it is changed then only it re-renders the template, otherwise it keeps old in-memory DOM in the same place. This way performance boost is gained.

6. Don’t try to use Angular for everything

Assume you got a requirement, you supposed to craft a page, where you should have stacked list of users when you move the mouse over each user item it should be highlighted with green color. This can be achieved in several different ways. But generally, people do look in for Angular way of doing it. One can also do use `ngClass` with some `customFlag` and that `customFlag` value is toggled based on `mouseover` and `mouseout` event. Hola! Hence, we have implemented a solution in an angular way. Over here we can add `highlight` flag over each element and toggle it based on mouseenter and mouseout event.

Component

 
import { Component } from '@angular/core'; 
@Component({ . . . }) 
export class AppComponent { 
items = [
 {id:1, name: 'Test'}, 
 {id:2, name: 'Test 2'} 
 ]
}
 

template

 <ul>
 <li (mouseenter)="i.highlight = true" (mouseleave)="i.highlight = false" [ngclass]="{highlight: i.highlight}" 
 *ngfor="let i of items">{{i.name}} </li>
 </ul>

Stackblitz here

Do you really think this is the perfect way of doing it? Think a bit! Okay, this is not a good way of doing such a thing. You must be wondering, why? If you look at closely, you have used two events for toggling the `highlight` variable that means you’re going to run two change detection cycle for each `mouseover` and `mouseout` events, ultimately change detection is gonna fired up multiple times based on how the user is interacting on this page.FYI Angular performs change-detection when:

  1. Event callback

  2. Network messages (XHR’s)

  3. Timer, Interval.

As already discussed change detection should run as less as possible for performance reason. So here on each mouseenter and mouseleave change detection fires most frequently. This could hamper performance in a huge application. The best way to solve this problem would be using native way, you can easily `:hover` pseudo-class to attach highlight behaviour. No need to use any kind of event or flag to maintain the hover status of the element.

Template

 <ul>
 <li *ngfor="let i of items">{{i.name}}</li>
 </ul>
 

CSS

li:hover { background: yellow }

“Don’t try to solve every problem Angular way, think simple, try out a native way as well”

7. Service Worker and Server-side rendering

Service worker is a new browser feature by which we can run event-driven scripts that can run independently. Ultimately it helps to improve performance by shifting some of the main thread tasks. Also, you can cache your API response and HTML pages, to boost up page load time. This feature can be leveraged to implement an offline mode of an application. Service worker provides access to `navigator` API which in turns open doors to get access to various native features like push notification, geolocation, etc.

There are many big giants always want to keep their users happy, so they completely focus performance metrics like TTIB, TFFR, etc. on the landing page to provide non-hesitating behaviour to end-user. For solving these problems, you can take benefit of navigator API and implement PWA to suppress some of these issues. You can use ` ng add @angular/pwa` to get ready PWA setup in an angular app. Another aspect you can look into is server-side rendering, this is a classic technique how we render our page. This technique can be used in Angular to improve time to first meaningful paint(FMP) metrics using Angular universal API.

What happens in server-side rendering is, when a request is sent to the server, initially page gets render on server side and server send fully render HTML to the client, and client renders it directly on the page. Thereafter as soon as client-side scripting kicks in and re-hydrate the page to make CSR code working. Server-side rendering significantly improves the initial page rendering. SSR is the most important aspect for SEO purpose and social link previewing. When crawler comes on the page to crawl, javascript is disabled when a page is loaded for a crawler. Server-side rendering can help to solve this problem. Though it has few gotchas that you have to take care of, thus SSR is not straight forward to implement. Check Angular Universal Docs.

Note

You should consider using server-side rendering only when you want fast First Meaningful Paint, SEO, and social link preview functionality.

Take Away

I hope you had learned about, how to get the hold on the performance of any angular application. By having these all optimization techniques incorporated inside your application could help you to gain huge performance win. If you have any further query, please feel free to ask. If you like this article, then don’t hesitate to share it with your peer fellows.

Crack Your Technical Interview

 
Learn in-demand Technologies
+