A drop in your traffic, lower interactions from your end-users, and high bounce rates are an indication of issues with the performance of your application.
As a business owner or project manager, one should identify what’s causing the issue and eliminate the cause. Sometimes, the goal is to optimize your application in such a way that the said issues will be resolved in the future.
Before we move onto Angular Performance Optimization, here are some common performance hurdles you may face, and how to get rid of them.
Some of the most common issues that you will face are a slowdown of the app, not getting expected results with migrated technology, slowdown of the app and loading of pages, unnecessary server usage, and errors in the real-time data stream.
Some of the common Angular performance issues can be removed by taking some simple steps. You can optimize the hosting with static cache content and using PWA, remove unnecessary recomputations, increase the size of bootstrap logic, use a service aggregator to slow HTTP requests, add OnPush wherever required, and remove unnecessary change detections that are slowing down the applications.
Now that you are aware of some quick fixes, let’s move on to Angular performance optimization, and how you can achieve it.
OnPush change detection is one of the most common features used for performance optimization. When there is change detection, this feature can act on separate branches, rather than starting from the root components.
Compared to this, default change detection in Angular will begin from the root component and run through the smallest subtree of your application. Naturally, the default change detection slows down the process. OnPush, on the other hand, selects which branch is supposed to go under change detection. There will be no rendering of any other root components or subtrees.
OnPush change detection will skip the subtrees that do not require rendering. For example, if two component trees have entries for odd numbers and even numbers, it allows developers to check the values only entered in the even numbers component tree. For operations that serve single purposes only, this change detection strategy makes so much sense.
On similar lines, there is immutability. OnPush detection does not mutate the objects directly. For the OnPush strategy to trigger the detection, we create a new object with a new reference.
If a component is initiated many times on the same page, rendering every one of those components will be expensive. In cases like those, you may need to turn off the automatic change detection and do it manually. Here, you will trigger change detection manually only in places where it is required.
In a template, when a component gets rendered, methods will be triggered every time. It will happen whenever there is an interaction with a component or a sub-component (click, type), even with onPush change detection.
Here, if the methods have to work with heavy computations, the app will slow down since there will be a recomputing every time an interaction occurs with the component.
The solution to this issue is to use pure pipes instead of methods. This will make sure that you are only recomputing when there is an input change to the pipe.
Lazy loading is when you only load the necessary modules at the initial loading to reduce bundle size and time taken. The application will load the app modules when they are necessary.
Especially for large-scale applications, there are a number of details and components that one needs to take care of. With more feature modules, it’s not necessary to load them all at once.
Lazy loading will decrease the size of code bundles and classify the modules based on their functionalities so they load only when they are required. There will be route navigation for downloading each code module.
Preloading works with Lazy Loading. In Lazy Loading, we load the feature modules when they are required. However, in preloading, we will load a module in the beginning since we know it is popular among the users.
Combined with Lazy Loading, you can decide when to start loading any specific feature module. There are two widely used preloading strategies.
PreloadAllModules which allows you to preload everything; and
NoPreloading where Angular will not preload anything.
Apart from these, you can write your custom strategies based on your requirements. Developers can specify the preloading strategy they want to imply by passing an option into the routing configuration.
AoT stands for Ahead of Time, and the AoT compilation is the pre-compilation of HTML and typescript code before the browser downloads it. This will help in faster rendering and will directly decrease the time taken to bootstrap the application.
It will also help you find template errors quickly, once again helping with the application payload.
NgFor is used for iterative operations to manage the array of iterable objects. However, when you use NgFor for updating the list, Angular removes the whole list from the DOM and will recreate it. You won’t be able to know what was removed or added to the list in such cases.
Moreover, larger applications will require larger lists to be updated and it will iterate the entire DOM for every element in it. This will negatively impact the application’s performance and speed.
trackBy is the best solution to this performance issue since it bifurcates the list items with unique identifiers. This limits the creation and destruction of DOM every time there is an update, and the action then is limited to only the items that have been changed.
If you need to load a component from a currently loaded component, any error will force you to navigate back to the previous component with an error. If the server responds with an error, some performance-impacting actions will take place.
All these actions will trigger DOM rendering and destruction, an expensive operation. Resolve Guard will help you ‘resolve’ this.
When an HTML call is sent within the Resolve Guar, the next component will be loaded only if the HTTP call is returned as ‘success’. In case of an error, the next component will not be loaded and the error will show up on the already loaded component. The time to render DOM and destroy it is saved, saving you time and increasing the performance.
Web Workers help you put complex processes into a separate thread other than the main thread. The user interface thus becomes smoother, and the user journey is not disturbed.
The complex processes may include complex calculations, content formatting in real time, image resizing or filtering, updates from the database, progressive web apps, encryption, etc.
When these complex processes are moved to separate threads, it helps the application run smoothly and the user does not have to wait for them to compute to move ahead in their journey on other UI elements.
To know when to run Change Detection on the component tree, NgZone is used to tap into async events. With that, the code that a developer writes in Angular will run in the Angular zone.
Now, if you are running a code that upsets UI every second, this will not be preferred. You will need to leave out updating the UI first and wait till you want to display the data you re-enter.
To get around it, you can run the code outside the Angular zone. The async events will not be picked by NgZone and the UI will not be updated.
processInsideZone runs the code in Angular and the UI will be updated because of that. To run the code outside Angular, processOutsideZone is there.
If your application fails to discard the resources that are not in use anymore, memory leaks may take place. The subscribe method with Observables uses a callback to get the values emitted. When you subscribe to observables, it creates an open stream and it will stay that way unless you unsubscribe from it. If they are not unsubscribed, they tend to create unnecessary memory leaks.
Lifecycle hooks like onDestroy can be used to unsubscribe the observables. This will make sure to close the stream whenever you leave the component.
Avoiding computations in template files can also help you optimize Angular. The functions run when the change detections run on the component and it needs to complete before change detection and other codes move on.
Here, if a function takes longer to finish, the UI will be slow and even laggy. Therefore, the function needs to complete before the other codes are executed for a smooth UI experience. If your functions are highly computational, move them to a bigger components file.
Angular applications, when not optimized correctly, can make your UI feel slow and laggy. It can deter your potential users away and stop them from returning to your website in the future. To avoid that, Angular performance optimization is necessary.
OnPush Change Detections, Detaching the Change Detection, Pure Pipes, Lazy Loading, Preloading, AoT compilation, DOM manipulation with TrackBy, Resolve Guards, Web Workers, and Optimizing Template Expressions are some of the ways to enhance your Angular product.
You can visit our news and projects on a weekly basis. Or you can subscribe to email for regular email updates.