Angular is a robust Javascript framework, and by default, it provides superfast performance. However, when designing complex and realistic applications, you may find that your application starts to become slow as the application size grows.
Meanwhile, if your application is not performing well, it will directly impact the end-user experience, which every programmer should avoid.
Further, it is essential to understand the underlying cause of poor performance in Angular applications with this thing in mind. First, you need to analyze your application and find the cause of the poor performance. Examine the code and determine if it followed the best practices. If not, you need to ensure that the application enforces the best rules and adheres to style guides and clean code architecture.
Here are some performance optimization techniques for improving the runtime of your Angular applications.
1. Lazy Loading of Modules
Lazy-loading is one of the most essential and most effective optimization tricks in the browser.
This is very effective; as a result it reduces the amount of bundled files loaded at the initial load of the webpage and only loads the resources that will be used directly in the webpage. All other resources doesn’t load.
They reload only when the user need them.
Angular provides a straightforward way to lazy-load resources.
To configure lazy loading in the Angular routers, we do this:
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'about',
loadChildren: () => import("./about/about.module").then(m => m.AboutModule)
},
{
path: 'service',
loadChildren: () => import("./service/service.module").then(m => m.ServiceModule)
}
]
@NgModule({
exports: [RouterModule],
imports: [RouterModule.forChild(routes)]
})
class AppRoutingModule {
}
2. Preloading
Preloading is an optimization strategy that loads resources for future navigations or consumption. This speeds up both the loading and rendering of the resource because the help will already be present in the browser cache.
Angular has a preloading strategy implemented in its router module. This allows us to preload resources, routes, modules, etc., in our Angular application. The Angular router provides an abstract class PreloadingStrategy that all class implements to add their preloading strategy in Angular.
Below is the code to configure the preloading strategy property in the router configuration.
// ...
RouterModule.forRoot([
...
], {
preloadingStrategy: OurPreloadingStrategy
})
// ...
3. “OnPush” Change Detection Strategy
Change detection is strongly related to Angular application performance. It is a convenient feature that helps a lot, but it can work against you if we do not misuse it.
The default behavior of change detection is that each change in your application leads to changing a single CD cycle and from top to bottom for all components.
The OnPush change detection strategy disables the change detection to run on a component and its children. Angular runs CD on the OnPush part when the app bootstraps and disables it. The Onpush element skips along with its children’s details in the subtree on subsequent CD runs.
If the inputs have referentially changed, change detection will only be run on the OnPush component.
@Component({
selector: 'newsletter',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class NewsletterComponent {
....
}
We can ask Angular to run CD by using detectChange method
@Component({
...
template: `<div>{{data}}</div>`
})
class NewsletterComponent {
data = 0;
constructor(private changeDetectorRef: ChangeDetectorRef) {
changeDetectorRef.detach()
}
clickHandler() {
this.data++
this.changeDetectorRef.detectChanges()
}
}
4. Use Pure Pipe
We use pipes to transform strings, currency amounts, dates, and other data for display. Pipes are simple functions to use in template expressions to accept an input value and return a transformed value. We can also pass additional arguments, like returning a difference of date from the start and end date.
Angular also provides some built-in pipes for typical data transformations, including conversion of date into a string, and transformations for internationalization (i18n), which use locale information to format data.
Pipes can be categorized into two types
- Impure Pipe
- It produces different output for similar input over time
- Pure Pipe
- It has a similar output for the same input
Pure pipes introduce no side-effects, so the behavior is predictable, and we can cache the input to shortcut CPU-intensive operations to avoid recomputing them.
Suppose we have used a function in a @Pipe that takes a reasonable amount of time before producing a result.
@Pipe({
name: "util"
})
class UtilPipe implements PipeTransform {
func(val) {
...
return ...;
}
transform(value) {
return this.func(value)
}
}
We will see that the func will hang the main thread that runs the UI and make the user wait to see the result. Now suppose the pipe is called every second; that will make a hell of an end-user experience.
To lower the execution of this pipe, we must first note the pipe’s behavior; if it does not change data outside its scope (outside the pipe) then the pipe is a pure pipe. We cache the function results and return them when next time the function call with the same input.
So for this, it is no matter how many times the pipe is called with an input, the func is called once, and the cached results are returned on subsequent calls.
To add this behavior in @Pipe, we do this:
@Pipe({
name: "util",
pure: true
})
class UtilPipe implements PipeTransform {
// ...
}
With this, we tell Angular that this pipe is pure and doesn’t side-effect, so it should cache the outputs and return them when the inputs occur again.
5. Use trackBy option for *ngFor directive
We are all familiar with the `*ngFor` directive, and it helps to repeat the same template for each collection item. Consider a scenario where you retrieve a list of accounts and display all account data on the page. Suppose a user adds a new account from the modal window; we reload the list of accounts again from the server and reassign the value to the list variable. This causes a re-rendering of `ngFor` directive.
As mainly one user add the accounts, we only accept lists that get appended with a newly created account when a new list is re-fetched; this will make `ngFor` unnecessarily re-rendering the DOM. The reason is that ngFor
removes all the DOM elements associated with the data and will re-create it even if the data is the same. Rendering an extensive collection on the page could be an expensive process.
To solve this problem Angular provide us the trackBy feature to track elements when they are added or removed from the array list, `trackBy` accepts a function that returns the identity to compare with so, while rendering the template for each iteration, it stores value to default value against each template, so next time before rendering the template, it checks for the trackBy function value, if it is changed then only it re-renders the template.
@Component({
...
template: `
<div *ngFor="let user of accounts; trackBy:trackByUserId">
...
</div>
`
})
class AccountComponent {
accounts = [];
constructor() {
}
trackByUserId(index: number, accounts: any): string {
return accounts.userId;
}
}
6. Optimize template expressions
Define your data/function in the component and render it on the DOM using double curly braces. For me, template expressions were one of the most useful features. Although we found it very useful, we still need to know the best practices for using Angular template expressions.
We often run functions in templates:
@Component({
...
template: `<div>{{getDate()}}</div>`
})
class TestComponent {
getDate() {
...
}
}
When CD runs on the TestComponent this getDate will also run. Also, this getDate will have to complete before the CD, and other codes will move on.
If the getDate function takes a long time to complete, users will experience drag or slowdowns. If a template expression becomes highly computational, don’t use it in the template or utilize caching on it.
7. NgZone or Outside the Angular
NgZone catches asynchronous operations from the Angular application. Meanwhile all code we write in Angular runs on the global zone; on the call stack Zone.js creates this zone to listen to async events (click, mouse move, etc.).
Further after losing the power of change detection, even if Angular has the feature to disable the zone JS, the UI will not update. But we also have another option: the run outside-Angular zone.
Firstly, let’s take a look at the use case. It might occur in any place where you fire timers frequently, like setInterval, setTimeout. Let’s say we have to animate a car moving on track inside our angular app. Our animation code to move a car is in setInterval.
@Component({
...
template: `<div> </div>`
})
class TestComponent {
interval: any;
constructor(private ngZone: NgZone) {}
processOutsideZone() {
this.ngZone.runOutsideAngular(()=> {
this.interval = window.setInterval(() => {
this.setPosition();
this.repaint();
}, 200)
})
}
}
Every 200ms, our car moves from its current position, and CD is also triggered each time. By default, CD is triggered whenever last function in call stack is executed — if it’s in the NgZone.
So, every 200ms, on the call stack we put a callback from set Interval , and after execution of this function, the CD triggers on the root component.
Further there is a simple way to fix it. You can wrap it with NgZone.runOutsideAngular. processOutsideZone runs the code outside the NgZone, and angular isn’t even “aware” that we fire these timers.
A place for big ideas.
Reimagine organizational performance while delivering a delightful experience through optimized operations.
Conclusion
There go the top 7 best practices to boost your Angular application performance.
To sum up, developing an Angular application is a straightforward task. Further, the challenging task is to optimize its performance for an excellent end-user experience. However, there are many ways of improving the performance of an application; incorporating some of the essential optimization techniques inside your web app development can help in optimizing and fine-tuning your Angular apps.