A very well-liked and widely used framework for creating contemporary web applications is Angular. This technology has a ton of features and is very powerful. As a web developer, you already have everything you need, and Angular makes it simple to configure, maintain, and grow any application created using the framework.
You’ve probably already created one or more Angular applications by this point, but are they ideal?
How to Improve The Performance of Angular Applications
I’ve created a sample Angular application that I’ll use in this article. The following features and libraries are used by the application as of the time this article was written:
Angular Build
When I run the application in a development environment, everything seems to be working perfectly, but the initial lighthouse score is not very good:
I can see where the problems are coming from when I look at the recommendations for enhancing the lower-scoring sections. The size of the resources (JavaScript, styles, and static resources) transferred to the client is the first significant problem.
Running a production build of my Angular application rather than a development one will quickly fix this problem.Prior to deployment, always build with the production configuration. This will take care of the warning to make JavaScript and CSS smaller. To see how the production build differs, let’s look at the angular.json file located at the root of our Angular repository:
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "10kb"
}
],
"fileReplacements": [
{
"replace": "projects/common/src/environments/environment.ts",
"with": "projects/common/src/environments/environment.prod.ts"
}
],
"localize": true,
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"serviceWorker": true,
"i18nMissingTranslation": "error",
"ngswConfigPath": "projects/bellumgens/src/ngsw-config.json"
},
"bg": {
"localize": [
"bg"
]
}
}
There are numerous configurations present. The “optimization”: true one is, however, the most crucial in this situation. In terms of load-time performance, the difference in score is sizable once I run the application with a production configuration:
The number of suggestions is significantly less if I take another look at the list of opportunities. The biggest opportunities for text compression are the caching of static resources and unused JavaScript:
Angular Pre-Rendering and Server-Side Rendering
A single page application (SPA) framework is Angular. By default, the app’s lifecycle is set up so that, in response to a client request, the server that hosts the application sends a file of HTML that contains all the required script and style references. However, the body content is missing. The application is bootstrapped and filled with content according to the JavaScript logic for the given application after the scripts and styles have been requested and served. In order to enhance this lifecycle, Angular offers two mechanisms that serve actual content in the initial HTML document. To accomplish this, the application’s JavaScript logic must be run before the document is served. How to go about 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.
For the Angular example app, I have enabled server-side rendering, and I’m using the express engine to enable text compression and static resource caching. The following is added to my express server logic to achieve this:
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 files from /browser with 1y caching
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
...
}
I’ll perform server-side rendering for the app and repeat the lighthouse test. The speed index was lowered to 1.2 seconds while the initial load improved even more, bringing the first contentful paint to under a second.
Reducing unused JavaScript and CSS is one of the final optimization opportunities for Angular.
To deal with these, I will have to do some application refactoring.
Angular Lazy-Loading
The best strategy for reducing the amount of unused JavaScript would be to create routes that are lazy-loaded. This will divide the JavaScript into modules, the logic for which is loaded only when the requested route is loaded, and inform the Angular framework which components are not required in the top-level module.
The Angular example app I’m using for this blog makes use of larger components that aren’t included in the home route, like the igx-grid. I’m creating a separate module for the routes that use this component. In this manner, the component will only be loaded after the routes that use it have been loaded. The routes appear as follows after the modules have been separated:
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'register', component: RegistrationComponent },
{ path: 'unauthorized', redirectTo: 'unauthorized/', pathMatch: 'full' },
{ path: 'unauthorized/:message', component: UnauthorizedComponent },
{ path: 'emailconfirm', component: EmailconfirmComponent },
{ path: 'strategies', loadChildren: () => import('./strategies/strategies.module').then(m => m.StrategiesModule) },
{ path: 'emailconfirm/:error', component: EmailconfirmComponent },
{ path: 'players', loadChildren: () => import('./player-section/player.module').then(m => m.PlayerModule) },
{ path: 'team', loadChildren: () => import('./team-section/team.module').then(m => m.TeamModule) },
{ path: 'notifications', loadChildren: () => import('./notifications/notifications.module').then(m => m.NotificationsModule) },
{ path: 'search/teams/:query', component: TeamResultsComponent },
{ path: 'search/players/:query', component: PlayerResultsComponent },
{ path: '**', component: HomeComponent }
];
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 { }
Looking at the build, the initial splitting produced the following:
Limiting the size of the CSS to the styles that are used is another thing that must be done to enhance the performance of the Angular application.
Optimizing Images
The images should be optimized as part of the Angular optimization process. You can use Lighthouse check to determine whether the type or size of your images are insufficient. Make sure the images you load are optimized for encoding and are not larger than the containers they go in. I now save my images in.webp format. Although it slightly lowers the quality, the application still does not require the highest fidelity images.
After being loaded, images cause changes to the layout. In order for the browser to be aware of the dimensions prior to loading the images, it is a good idea to set width and height attributes on the images. Lighthouse will alert you if the images’ aspect-ratio settings (width and height) are missing. The layout shifts will be lessened or eliminated as a result.
NgOptimizedImage to Enforce Image Best Practices
For your benefit, Angular exposes a directive that upholds image best practices and streamlines image loading. Its name is NgOptimizedImage, and using it is fairly simple. If your Angular application is still using NgModules, all you have to do is import CommonModule, or import NgOptimizedImage in the component you want to use it in. Then you use ngSrc to set the image source attribute rather than src.
<img ngSrc="/assets/wallpapers/strat-editor.webp" width="600" height="347" class="preview-image" alt="Strategy Editor" />
Finally, you can specify the order in which each image should load and instruct the app to lazy-load all non-critical images. If I run the app without the width and height attributes, what I get is:
And that’s it.
Final Verdict
In order to ensure that Angular applications operate effectively and dependably under a variety of circumstances, performance improvement may necessitate ongoing monitoring, optimization, and best practices. But in the end, this is how you provide the best UX, draw in and keep users, stay competitive, and grow your business.