How to Optimize your Angular App
Angular is a great framework and is well suited for developing large apps built to get the highest performance possible on the web. But sometimes we as a developer end up doing things that result in the poorly performing app.
In this post, I will share the optimized technique in two performance ways.
1. Load Time Performance
A. Lazy Loading
With lazy loading, we can split our application to feature modules and load them on-demand. The main benefit is that initially, we can only load what the user expects to see at the start screen. The rest of the modules are only loaded when the user navigates to their routes.
The best way to improve your lazy loading is… we load only the modules which are required at the moment with reducing the initial load time. In simple words, it means “don’t load something which you don’t need”.
E.g. we register the lazy loaded module. When the user navigates to ‘/auth’. Angular will load AuthModule located in ‘../app/pages/auth/auth.module’.
B. AOT (Ahead-of-Time)
AOT: As opposed to JIT Compilation where the compilation is done in the browser, AOT compiles much of the code during the build process (also called offline compilation) thus reducing much of the processing overhead on the client browser. With angular-cli just specify the “aot” flag (if the prod flag is present, then the aot flag is not required) and AOT will be enabled.
E.g. we register into two sections (production & staging) within aot is true. Then into fileReplacements we directly go to the file environment to replace the main file (environment.ts) -> environment with flag’s condition.
C. Build-optimizer flag
If you are using angular-cli make sure you specify the “build-optimizer” flag for your production build. This will disable the vendor chunk and will result in a smaller code.
E.g. we compile into two sections (production & staging).
ng build — configuration=staging
ng build — configuration=production
D. Uglify
It is the process where the code size is reduced using various code transformations like mangling, removal of white spaces, removal of comments, etc. For webpack use uglify plugin and with angular-cli specify the “prod” flag to perform the uglification process.
white spaces e.g. a = b * d — c; -> is equivalent to -> a=b*d-c;
comments e.g. actually we don’t need to explain everything about our syntax. Only comment syntax which is a difficult process or we need to track it.
E. Tree-shaking
This is the process of removing unused code resulting in smaller build size. If you are using angular-cli, Tree-Shaking is enabled by default.
F. Webpack
Using Webpack 4 (and higher) for your angular-cli or custom webpack build results in smaller build size compared to Webpack 3. Webpack 4 has mode option which lets you specify the optimization type (production or development) without you requiring to write any explicit configuration giving you the best possible results for the target environment. Also, build time with Webpack 4 is much faster (60% to 98%) than the earlier version thereby reducing the development time.
To check webpack version we can type :
npm view webpack version
To install or update the latest webpack, we can type :
npm install — save-dev webpack
G. Server-side rendering
Server-side rendering: Rendering the first page of your application on the server (using Node.js, .Net, PHP) and serving it as a static page causes near to instant rendering thus greatly improves perceived performance, speed, and overall user experience. You can use Angular Universal to perform server-side rendering.
H. Progressive Web App
Progressive Web App: PWA makes your app load much faster, it gives the offline capability to your app and gives near-native app experience thus greatly improving overall perceived performance by the user.
> Makes your angular app a PWA
From a terminal, with the Angular CLI installed globally, run the following command:
ng new pwa-example
This just sets up a default, out-of-the-box, “Hello World” Angular application. Alternatively, generate a new project with any properties you like or run the next command in an existing Angular project.
Change directories to your new project folder, and run this command:
ng add @angular/pwa && ng build — prod
and just like that, your Angular application is a production-ready PWA!
I. Updating Angular and angular cli
Updating your Angular and angular-cli regularly gives you the benefit of many performance optimizations, bug fixes, new features, security etc.
To check your current angular version, you can type :
ng — version
To update the latest version of angular, we can type :
ng update @angular/cli @angular/core
To update angular with a version, we can type :
ng update @angular/core@8 @angular/cli@8
J. RxJS 6
RxJS 6 makes the whole library more tree-shakable, by reducing the final bundle size. However, it has some breaking changes like operators' chaining is not possible instead, pipe() function (helps in better tree shaking) is introduced to add operators. They have also renamed some operators.
> Observable
Angular uses observable which is implemented with RxJS library for work with asynchronous events. It is a new way of pushing data in JavaScript. An observable is a Producer of multiple values that pushing values to subscribers. import { Observa } from ‘rxjs’;
> Observer
An Observer is a consumer of values delivered by an Observable. Observers are simply a set of callbacks, one for each type of notification delivered by the Observable: next, error, and complete.
To use the Observer, provide it to the subscribe of an Observable:
> Operators
Operators are functions. There are two kinds of operators:
Pipeable Operators are the kind that can be piped to Observables using the syntax observableInstance.pipe(operator()). These include filter(…), and mergeMap(…). When called, they do not change the existing Observable instance.
Instead, they return a new Observable, whose subscription logic is based on the first Observable.
> Subscription
A Subscription is an object that represents a disposable resource, usually the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes of the resource held by the subscription. In previous versions of RxJS, Subscription was called “Disposable”
> Subject
An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.
> Scheduler
A scheduler controls when a subscription starts and when notifications are delivered. It consists of three components.
· A Scheduler is a data structure. It knows how to store and queue tasks based on priority or other criteria.
· A Scheduler is an execution context. It denotes where and when the task is executed (e.g. immediately, or in another callback mechanism such as setTimeout or process.nextTick, or the animation frame).
· A Scheduler has a (virtual) clock. It provides a notion of “time” by a getter method now() on the scheduler. Tasks being scheduled on a particular scheduler will adhere only to the time denoted by that clock.
K. Service worker cache
If you have configured your app to support Progressive Web App, make sure to specify all the necessary static resources in the PWA config JSON. These static files will be cached in the client’s browser making the second time load much faster.
L. Ivy Render Engine
The angular team recently announced a new render engine named Ivy. It results in a much smaller bundle size than the current engine with improved debugging experience. Though it is still not production-ready you can still try it in your app. You can look at this ng-conf keynote for more details.
M. Third-party packages
Review the third-party packages you are using and see if the better and smaller alternative is available as it may reduce the final size of your build.
N. Unnecessary use of third-party packages
If you include a third-party package just to achieve a small functionality that could be easily done natively with JavaScript or Angular then you are adding unnecessary size overhead to your app which could have been easily saved. For example, if you are including Lodash just to do a simple object filtering then it is totally unnecessary as you could do the same thing natively in JavaScript.
O. Defer attribute
Mentioning defer attribute to your script tag will defer the loading of the scripts (synchronous) until the document is not parsed thus making your site interactive quicker. For angular-cli app currently, there is no way to add this automatically during the build, you have to do it manually after the build.
P. Async attribute
Just like the defer attribute, async delays the loading of scripts until the document is not parsed but without respecting the order of loading of the scripts. The best example to use it with google analytics script which usually independent of any other scripts.
Q. Compressing images
It’s a good idea to compress the images without losing much of the quality thereby saving the bytes transferred over the network improving the build time. There are many tools available to achieve this. VS Code extension called TinyPNG can be used to compress Jpeg and PNG images without losing much of the quality.
R. Remove unused fonts
It’s a good idea to remove the unused fonts which may help you save a few bytes over the network.
2. Runtime Performance
A. TrackBy
Rendering lists can affect the performance of an application — huge lists with attached listeners can cause scroll jank, which means your application stutters when users are scrolling through a huge list. Another issue with lists is updating them — adding or removing an item from a long list can cause serious performance issues in Angular applications if we haven’t provided a way for Angular to keep track of each item in the list.
To help Angular handle the list properly, we’ll provide a unique reference for each item contained in the list using the trackBy function. Let’s look at an example: A list of items rendered in a component called UsersComponent. Let’s see what happens in the DOM when we attempt to add an extra item with and without the trackBy function.
Without providing a unique reference using trackBy, the elements rendering the user list are deleted, recreated, and rendered on the click of the Add user button. We can make this more performant by including the trackBy function.
Update the rendered list to use a trackBy function and also the component to include a method that returns the id of each user.
B. Change Detection
In your application, Angular runs checks to find out if it should update the state of a component. These checks, called change detection, are run when an event is triggered (onClick, onSubmit) when an AJAX request is made, and after several other asynchronous operations. Every component created in an Angular application has a change detector associated with it when the application runs. The work of the change detector is re-rendering the component when a value changes in the component.
This is all okay when working with a small application — the number of re-renders will matter little — but in a much bigger application, multiple re-renders will affect performance. Because of Angular’s unidirectional data flow, when an event is triggered, each component from top to bottom will be checked for updates, and when a change is found in a component, its associated change detector will run to re-render the component.
Let’s see an example:
You have a component named UserProfile. This component takes an Input object user, which contains the name and email of a user profile:
Now, this component is being rendered by a parent component User that updates the name of the user on the click of a button:
On the click of that button, Angular will run the change detection cycle to update the name property of the component. This isn’t very performant, so we need to tell Angular to update the UserProfile component only if one of the following conditions is met:
> Change detection is run manually by calling detectChanges.
> The component or its children triggered an event.
> The reference of the Input has been updated.
> This explicitly makes the UserProfile component a pure one.
Let’s update the UserProfile component to enforce these conditions by adding a changeDetection property when defining the component:
After making this update, clicking the Update Name button will have no effect on the component unless we also change the format by which we update the name of the user profile. Update the updateName method to look like the snippet below:
Now, click the button works because one of the conditions set is met the Input reference has been updated and is different from the previous one.
C. Detaching the Change Detector
Every component in an Angular project tree has a change detector. We can inject this change detector (ChangeDetectorRef) to either detach the component from the CD tree or attach it to the CD tree. So, when Angular run CD on the component tree, the component with its sub-tree will be skipped.
This is done by the use of the ChangeDetectorRef class.
markForCheck: When a view uses the OnPush (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again. Components are normally marked as dirty (in need of rerendering) when inputs have changed or events have fired in the view. Call this method to ensure that a component is checked even if these triggers have not occurred.
detach: Detaches this view from the change-detection tree. A detached view is not checked until it is reattached. Use in combination with detectChanges() to implement local change detection checks. Detached views are not checked during change detection runs until they are re-attached, even if they are marked as dirty. detach
detectChanges: Checks this view and its children. Use in combination with detach to implement local change detection checks.
checkNoChanges: Checks the change detector and its children, and throws if any changes are detected. Use in development mode to verify that running change detection doesn’t introduce other changes.
reattach: Re-attaches the previously detached view to the change detection tree. Views are attached to the tree by default.
Example, we have this component:
We called the detach from the constructor because that’s the initialization point so the component is detached from the component tree at startup. CD runs on the entire component tree won’t affect TestComponent. If we change a template-bound data in the component, we need to reattach the component, so the DOM is updated on the next CD run.
This does that:
D. Console.log()
Using console.log() statements in your production code could be a bad idea as it will slow down the performance of the app and also logging objects with console.log() creates a memory leak issue. When the browser’s console window is open, the console.log() execution slows down even further by many times thus impacting the site’s performance significantly. It’s better to completely remove the console.log() statements from your production code or at least have environment-specific conditional logging.
G. Global Variables
There are many disadvantages of using global variables and one of them is the memory leak. The variables defined in the global scope won’t be cleared until the window is reloaded or tab is closed thus resulting in the memory leak if the global variable is not intended to be used throughout the app. If for some reason you want to have global variables, there are better ways to do it in the Angular.
F. Event listeners
Adding event listeners to your DOM node could create a memory leak issue. If you forget to remove the listener inside the $destroy event of your directive, it will hold a reference to a DOM node even if it is removed from the document. The DOM tree will then become a “Detached DOM tree” and will leak. Modern JS engines are able to figure most of these situations for you and remove the listeners, but more complex tree hierarchies can challenge even the best GC.
Contributor: Tunggal
Check our article on Comparison of Angular Admin Template
Sources :
https://angular.io/
https://rxjs-dev.firebaseapp.com/
https://blog.bitsrc.io/10-tricks-to-optimize-your-angular-app-44208f616bf0
https://www.grapecity.com/blogs/14-ways-to-speed-up-your-angular-app
https://itnext.io/how-to-optimize-angular-applications-99bfab0f0b7c