How to optimize Angular applications

Garen Stepanyan
ITNEXT
Published in
7 min readNov 26, 2018

--

Angular is the most popular framework for building single page web applications. Although i said single page but it doesn't necessarily mean that your application can contain only one page. You can create a website with dozens of pages using Angular. The framework itself (latest versions) is well optimized thanks to amazing team and community, however when it comes to performance we should always think about few things that can make our application run fast and smooth.

#1 Optimize main bundle with Lazy Loading

When we build our application in production without lazy loading, most likely we’ll see these files generated in dist folder

polyfills.js
scripts.js
runtime.js
styles.css
main.js

polyfills.js is for making our application compatible for different browsers. Because we write the code with newest features and not all browsers support such features.

scripts.js contains the scripts we declare in the scripts section of angular.json file

"scripts": [
"myScript.js",
]

runtime.js is the webpack loader. This file contains webpack utilities that are needed to load other files

styles.css contains all styles we declare in styles section of angular.json file

"styles": [
"src/styles.css",
"src/my-custom.css"
],

main.js contains all our code including components (ts, html and css codes), pipes, directives, services and all other imported modules (including third party).

As you can see over time main.js file will be bigger and bigger which is a problem as in order to see the website browser needs to download main.js file, execute, and render on the page which not only can be challenging for mobile users with slow internet, but also for desktops.

The easiest way of solving this issue is to split your application into several lazy modules. When we use lazy modules for each module angular generates its own chunk and it won’t be loaded until needed (typically by activating a route).

To demonstrate this i’ve created two components, app.component and second.component. Both are in app.module so there is nothing lazy here. App.component is very simple and have two buttons for navigating to Second.component and back to App.component.

However the second component contains very large text (about 1 mb) in template.

Because there is no laziness, when we build our application we get big main.js which contains all code both from app.component and second.component.

Now in the network tab of chrome dev tools we can see that indeed main.js is too big (1.3 mb)

The problem is that most of the time users visit the main page, rather than a specific page, so loading all code of other pages is not the best solution. We can create lazy module for second component and that module will be activated only when needed (i.e. only when the user navigates to that page). This gives us very small main.js and so very quick first load of main page.

When we use lazy loading, after build process new file will be created, like 4.386205799sfghe4.js. This is the chunk of that lazy module and it won’t be loaded at startup. Now when we open the app we see that main.js is very small (266 kb)

And only when we navigate to second page we see that new file (1 mb) is loaded

However loading each piece in such way will also affect on performance as initial navigation will be slower. Luckily Agular provides a way to solve this issue too with PreloadingStrategy. We can say Angular to load our main module (main.js) and when its fully loaded and executed, only after that load other lazy modules in the background, so when users navigate to lazy pages everything will be already downloaded. Example code for preloading all modules

import { PreloadAllModules, RouterModule } from '@angular/router';RouterModule.forRoot(
[
{
path: 'second',
loadChildren: './second/second.module#SecondModule'
}
], {preloadingStrategy: PreloadAllModules})

So always consider using as many lazy modules as possible with some preloading strategy. This will keep your main.js small which means faster download and render of main page.

#2 Debug bundles with Webpack Bundle Analyzer

Even if after splitting the logic of application into many lazy modules you get large main bundle ( as ‘large’ i personally consider greater than 1 mb for small-mid apps), you can optimize further using Webpack Bundle Analyzer. This npm package allows to visualize size of webpack output files with an interactive zoomable treemap. First of all install the plugin as a dev dependency in your angular project

npm install --save-dev webpack-bundle-analyzer

Then modify your package.json file with adding this line in scripts section

"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"

Note that dist/stats.json can be different in your project . For example if your bundle files are generated in dist/browser, you need to modify the command as dist/browser/stats.json

Finally run

npm run bundle-report

This will create production build with statistics about each bundle and with help of webpack bundle analyzer we can visualise that with zoomable treemap.

From here we can see what modules/files are used in each bundle. This tremendously helps as we can visually see what is included that shouldn’t’ve to be there.

#3 Create several small shared modules

It’s considered best practise to have shared modules for DRY, but sometimes shared module also can get bigger and bigger. For example if we have SharedModule that contains many other modules/components/pipes, importing such module in app.module will increase the bundle size of main.js, because we will import not only what main module needs, but also all other unnecessary stuff that comes with SharedModule. To avoid this we can create another shared module, HomeSharedModule which will contain only components that main module and its components need.

Having multiple shared modules is better than one big shared module.

#4 Use Lazy Loading for images that are not visible in page

When we load our main page first time we can have images that are not visible for the user (not in viewport). The user has to scroll down to see images. However images are downloaded right away when we load the page and if we have many images, this can really affect on performance. To solve this issue we can lazy load images, load only when the users gets to them. There is a JavaScript API - Intersection Observer API which makes it really easy to implement lazy loaded content. Furthermore we can create directive for reusability. Here is nice article about it.

#5 Use virtual scrolling for large lists

Version 7 of framework introduced virtual scrolling in CDK. Virtual Scrolling loads and unloads elements from the DOM based on the visible parts of a list, making our application extremely fast.

Instead of loading and displaying full list, we can display only few items that are visible at that time

#6 Use FOUT instead of FOIT for fonts

In most websites we see custom beautiful fonts, rather than regular fonts. However using custom or font provided by another service requires browser to download and parse that font when the user visits our page. There are two scenarios what will happen if we use custom fonts provided by 3-rd party services like Google Fonts
1. Browser waits to download the font, parse it and only then displays the text on page. The text on page will be invisible until the font is not downloaded and parsed. This is FOIT, or Flash of invisible text .

2. Browser initially displays the text in regular font and also tries to fetch external font styles. When downloaded and parsed it will swap regular font with our custom font. The text on page will be rendered with regular font, meanwhile if the browser will download and parse external font, fonts will be swapped. This is FOUT, or Flash of unstyled text

Most browsers use FOIT and only Internet Explorer uses FOUT . To fix this we can use font-display descriptor for @font-face and tell browser if we want to go with regular font and then swap or leave text invisible. You can also read this article which explains how fonts are working and in which cases you need FOIT and FOUT

There are many other techniques we can use for better performance, including Server Side Rendering, Service Workers, AMP pages and much more about which i’ll talk in next article

--

--