Angular is a TypeScript-based framework for building scalable web applications.
Angular is self-declared as the modern web developer’s platform and is nothing short of absolute facts. Angular facilitates users to build custom functional components that can be reused.
Besides the obvious advantages of using a frontend framework and regular updates, Angular boasts an uncomplicated CLI with simple commands, Server-side rendering with Angular Universal and support from Google.
In this blog, we will learn about how you can optimize your angular apps effectively.
Why is it necessary to optimize the performance of angular apps?
Though designed to make frontend development easier, Angular has shortcomings.
Building complex applications with much content in Angular tends to slow down at the user end, especially if the proper methodology is not followed during development. Unlike React, Angular is neither flexible nor easy to learn. It has a rigid structure and is demanding to work with.
If done right, Angular can be an advantageous framework that facilitates the development of highly performant apps. Therefore it is necessary to optimize angular apps.
The solution: Optimization
Optimization is a standard resolution to angular performance tuning. The optimization involves altering your application’s code for better performance and easier maintenance.
One common misconception is that optimization begins after your application has been deployed. The process should start right when you start writing your code; otherwise, it becomes harder to reverse mistakes.
Likewise, you need to optimize angular apps for the clean execution it promises. To aid your next project in angular, we’ve compiled an Angular app optimization checklist, which can help improve performance of angular apps.
Methods to Optimize Angular Apps
AoT Compilation
As mentioned earlier, Angular is based on Typescript. To compile Typescript to JavaScript- there are two modes:
- Just-in-Time Compilation (JIT), Which involves compiling during execution. Here, the program is compiled to native code (here, JavaScript) only when a function is called. This is beneficial for cross-platform development. However, JIT is disadvantageous as it can take time to render larger components.
- Ahead-of-Time Compilation (AoT), which involves compilation during build time. This removes the requirement for an Angular compiler in the deployment bundle, reducing the size of the app and the time required to render individual components, and boosting the app’s performance.
Hence a key step to optimizing angular apps is choosing AoT Compilation.
AoT can be implemented by following:
Here, the @Component() metadata object and the class constructor lets Angular know how to create and display an instance of TypicalComponent.
NB: @Component() is a decorator used to explicitly mention metadata.
@Component({ selector: ‘app-typical’, template: ‘<div>A typical component for {{data.name}}</div>’ }) export class TypicalComponent { @Input() data: TypicalData; constructor(private someService: SomeService) { … } } |
Change Detection
Change Detection is a feature that enables you to detect when the user’s data has been altered and subsequently update the component data to reflect those changes. In the case of large applications, the frequency of detection of change hinders performance.
To overcome this, any of these three strategies can be implemented:
OnPush Change Detection Strategy
In default mode, the detection happens from the root component to the smallest subtree, slowing down the entire process. The OnPush strategy chooses only the required branch to go under change detection and spares the rest of the branches and root components.
@Component({ selector: ‘app-root’, template: `Number of ticks: {{numberOfTicks}}`, changeDetection: ChangeDetectionStrategy.OnPush, }) class AppComponent { numberOfTicks = 0; constructor(private ref: ChangeDetectorRef) { setInterval(() => { this.numberOfTicks++; // require view to be updated this.ref.markForCheck(); }, 1000); } } |
Detach Change Detector
In Angular, each component of a tree has a Change Detector attached to it. With bigger apps using multiple components, this drags out the rendering time. One solution to improve the load time in such cases is to remove the Change Detector attached to certain components such that the associated subtrees are skipped.
This can be done by calling the class ChangeDetectorRef:
abstract class ChangeDetectorRef { abstract markForCheck(): void abstract detach(): void abstract detectChanges(): void abstract checkNoChanges(): void abstract reattach(): void } |
Using Pure Pipes
In Angular, Pipes are simple functions to use in template expressions to accept an input value and return a transformed value. There are two types of pipes, pure and impure. Methods whose result only depends on their input arguments are pure pipes.
The fundamental difference is that an impure pipe can produce different output for similar kinds of input, whereas a pure pipe produces similar output for the same input. All methods are pure by default. Therefore, by using pure pipes, change detection is only triggered when the value changes and the new value is passed onto it.
@Pipe({ name: ‘filterPipe’, pure: true }) export class FilterPipe {} |
Lazy Loading
Lazy loading is a default feature of Angular that improves application performance. Complex applications tend to have many feature modules. If all these modules are loaded right when the application is launched, the app slows down and takes up a sizeable amount of space.
Lazing loading refers to the loading of only those modules that are required during the initial load while other modules are loaded only when the user accesses them.
Lazy loading can be carried out by using loadChildren in place of component, in AppRoutingModule routes configuration:
const routes: Routes = [ { path: ‘items’, loadChildren: () => import(‘./items/items.module’). then(m => m.ItemsModule) } ]; |
Now add a route for the component in the newly lazy loaded module’s routing module
const routes: Routes = [ { path: ”, component: ItemsComponent } ]; |
Web Workers
Javascript is single-threaded, i.e., it has a main thread that runs on the browser. But when it comes to applications that require loading of events such as rendering graphs or complex calculations, the app might slow down and compromise on performance.
To avoid this, JavaScript provides a structure of code known as Web worker that creates threads that parallel to the main thread in order to run these tasks. Hence the apps run smoothly with a lesser number of halts.
A web worker follows the given code format:
if (typeof Worker !== ‘undefined’) { // Creating a new worker const worker = new Worker(‘./app.worker’, { type: ‘module’ }); worker.onmessage = ({ data }) => { console.log(`message: ${data}`); }; worker.postMessage(‘Web workers at work…’); } else { // fallback mechanism so that your program still executes correctly } |
Unsubscribing from Observables
Observables are methods in RxJS that provide support for data sharing between publishers and subscribers in Angular. This support includes event handling, asynchronous programming and handling multiple values.
The function is defined in all angular apps, but can only be accessed by consumers who subscribe to it. But subscribing to observables with weaknesses such as memory leaks. Subscription requires global declaration of variables leading to said memory leaks.
Routinely checking and unsubscribing to these is necessary to ensure better performance.
One method of unsubscribing is:
let subs: Subscription[] = []; ngOnInit() { this.subs.push(this.service.Subject1.subscribe(() => {})); this.subs.push(this.service.Subject2.subscribe(() => {})); } ngOnDestroy() { subs.forEach(sub => sub.unsubscribe()); } |
Preloading Modules
Lazing Loading does good optimization of angular apps by loading modules as per a user’s needs. But this can have an undesirable effect when the router has to fetch other modules from the server which ends up taking more time.
Preloading serves as a solution to this problem. With preloading the router loads all the modules in the background beforehand while the user interacts with only the lazy loaded modules.
Preloading can be incorporated by adding:
abstract class PreloadingStrategy { abstract preload(route: Route, fn: () => Observable<any>): Observable<any> } |
Tree Shaking
Tree-shaking is the methodology used for removing unnecessary dead code from JavaScript. Dead code refers to modules that are left unused during build time. This aids in minimizing build size to the maximum and thereby optimizing the app size.
Tree-Shaking is enabled by default if you use Angular’s CLI.
To enable Tree-Shaking, use the following command:
<listing> ng build –prod <listing> |
Conclusion
Angular’s benefits, sometimes also doubles up as its drawbacks. For instance, being a very structured framework, there’s always a definite answer to common problems that occur while development, but at the same time, the lack of flexibility inhibits developers from tinkering around and finding more custom solutions to their issues.
The right team of developers know that optimization of Angular applications should go hand in hand with its building phase to ensure maximum efficiency on every end.