How To Improve the Performance of Angular Apps
How do you improve the performance of Angular apps? Read this blog post to find the ways – from reducing unused JavaScript and CSS to Angular lazy loading.
Join the DZone community and get the full member experience.
Join For FreeAngular has become a very popular and widely adopted framework for developing modern web applications. This technology is both very powerful and feature-rich. Everything that you need as a web developer comes out of the box, and Angular allows for easily configuring, maintaining, and expanding any application built on top of the framework.
And by now, you’ve probably already put together one or more Angular applications, but are they optimal?
In Part 2 of my Software Performance series, then, I will talk about Angular optimization, demonstrating how to improve the performance of an Angular application using an Angular example app that I built. I will use Chrome Dev Tools to derive an initial lighthouse score to determine where the application initially stands. Let’s take a look at what can be improved and how.
How To Improve the Performance of Angular Applications
For this article, I will use a sample Angular application that I’ve put together. At the time of writing this article, the application uses the following features and libraries:
- Angular 16
- Ignite UI for Angular 16
- RxJS 7
- PWA (Angular service worker)
- Server-side rendering (express server)
- Angular localization
Angular Build
Everything seems to be running just fine when I’m running the application in a development environment, but the initial lighthouse score is not very high:
When I look at the suggestions for improving the lower-scoring sections, I see where the issues are coming from. The first big issue is the size of the resources (JavaScript, styles, static resources) that are transferred to the client.
This issue is resolved easily by running a production build of my Angular application rather than a development one. Always build with production configuration prior to deployment. This will resolve the warning to reduce the size of JavaScript and CSS. Let’s take a look at the angular.json file at the root of our Angular repository to see how the production build differs:
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "10kb"
}
],
"fileReplacements": [
{
"replace": "projects...Code
There’s quite a lot of configuration here. However, the most important one, in this case, is the "optimization": true
one. Once I run the application with a production configuration, the difference in score is significant in terms of load-time performance:
If I look at the opportunities list again, the number of suggestions is much lower. The biggest opportunities listed at about text compression are unused JavaScript and caching of static resources:
Angular Pre-Rendering and Server-Side Rendering
Angular is a Single Page Application (SPA) framework. By default, the lifecycle of the app is such that upon a request from a client, the server that hosts the application serves down an HTML file that includes all the necessary script and style references. However, it is empty of body content. Once the scripts and styles are requested and served, the application is bootstrapped and populated with content based on the JavaScript logic for the given application. Angular provides two mechanisms to improve this lifecycle by serving actual content in the initial HTML document. To do this, the JavaScript logic of the application needs to be executed prior to serving the document. The ways to do it:
- Either at build time (pre-rendering) - for pages with mostly static content.
- Or at run time on the server (server-side rendering) - for pages with more dynamic content that need to deliver up-to-date content on every request.
I have enabled server-side rendering for the Angular example app, and I’m using the express engine to enable text compression and static resource caching. This is done by adding the following to my express server logic:
export const app = (lang: string) => {
// server scaffolded by [ng add @nguniversal/express-engine]
...
// enable compression [npm install compression]
const compression = require('compression');
server.use(compression());
...
// Serve static...Code
I will serve the app with server-side rendering and run the lighthouse test again. The initial load improved even further, bringing the first contentful paint to under a second, while the speed index is reduced to 1.2 seconds.
The leftover opportunities for Angular optimization are to reduce unused JavaScript and CSS.
To deal with these, I will have to do some application refactoring.
Angular Lazy-Loading
In order to reduce the unused JavaScript, the best approach would be to create lazy-loaded routes. This will tell the Angular framework which components are not needed in the top-level module and will split the JavaScript into modules, the logic for which is loaded only when the requested route is loaded.
The Angular example app that I’m using for this blog utilizes larger components, like the igx-grid, which are not part of the home route. I’m separating the routes using this component into a separate module. This way, I’m going to have that component loaded only after the routes using the component are loaded. After separating the modules, the routes look like this:
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'register', component: RegistrationComponent },
{ path: 'unauthorized', redirectTo: 'unauthorized/', pathMatch: 'full' },
{ path: 'unauthorized/:message', component...Code
The team.module
is the one loading the grid I’m using, so the code for it looks like this:
@NgModule({
imports: [
CommonModule,
FormsModule,
// ...
IgxGridModule,
// ...
TeamComponent,
// ...
]
})
export class TeamModule { }Code
Looking at the build, this is the result of the initial splitting:
Another thing that needs to be done to improve the performance of the Angular application is to limit the size of the CSS to the styles that are used. I’m using Ignite UI for Angular as my UI library and there’s a great how-to article on customizing and optimizing the component themes.
Optimizing Images
Another aspect of the Angular optimization that should be done is optimizing the images. Lighthouse check will tell you if you’re using suboptimal images by type or size. Make sure the images you load are not larger than the containers they go in and they are with optimal encoding. I now use .webp format for images. It reduces the quality a little, but still, the application does not require the highest fidelity images.
Images cause layout shifts after they are loaded. Hence, it’s a good idea to set width and height attributes on the images so the browser knows the dimensions before loading the images. If you’re missing aspect-ratio settings (width and height) on the images, Lighthouse will warn you. This will reduce or completely eliminate the layout shifts.
NgOptimizedImage to Enforce Image Best Practices
Angular exposes a directive that enforces image best practices and optimizes image loading for you. It’s called NgOptimizedImage and it is rather easy to use. All you need to do is import CommonModule
if you’re still in an NgModule
based Angular application or import NgOptimizedImage
in the component where you want to use it. Then, instead of using src
to set the image source attribute, you use ngSrc
instead.
Code
Finally, you can further specify the loading priority for each image and tell the app to lazy-load every non-critical image. If I remove the width and height attributes, then what I get running the app is:
And that’s it.
Wrap Up…
Improving the performance of Angular applications may require continuous monitoring, optimization, and best practices to ensure the app performs efficiently and reliably under various conditions. But in the end, this is how you deliver the ultimate UX, attract and retain users, maintain competitiveness, and achieve business success.
Published at DZone with permission of Konstantin Dinev. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments