9 Ultimate Tips to Optimise Angular Performance

During a few years development experience with Angular, I have concluded a few fundamental tips/best practices to improve Angular app (Or web development) performance. Some of the tips are Angular-only, while some of them are universal for web development.
In this article, I will list 9 tips to improve your Angular app performance in JavaScript perspective.
If you feel something is important but missed in this article, it’s always welcomed to leave a comment and share with us.👏👏👏
Javascript Improvements
1.1 Lazy loading
For commercial app, your Angular probably consists of a few modules, each one is a big chunk of loading delay. To solve this, you can use lazy loading as suggested by the offical documentation.
const routes: Routes = [
{
path: 'lazy',
loadChildren: () => import('./my.module').then(m => m.MyModule)
}
];1.2 ChangeDetectionStrategy.OnPush
When you create an Angular component, you can specify the changeDetection strategy to Default or OnPush. The former one is default and will be triggered every time when a change detection happened. OnPush, instead, will only run when it detected a change, e.g. @Input value change or a function() inside the component, which saves a lot of unnecessary re-rendering.
@Component({
selector: 'app-comp',
templateUrl: './app-comp.component.html',
styleUrls: ['./app-comp.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})1.3 Unsubscribe/takeUntil
When you have an observable inside your component which can keep emitting values and it complete on it self, you will need to unsubscribe it on component destroy to avoid memory leaks. Or, you can use takeUntil from RxJs.
dataHandler() {
this.subscription = data$.subscribe();
// takeUntil solution
// data$.pipe(takeUntil(this.destroy$)).subscribe();
}ngOnDestroy() {
this.subscription.unsubscribe();
// takeUntil solution
// this.destroy$.next();
}1.4 AsyncPipe
Followed with the last point, another way to handle the subscription and un-subscription is to let Angular do it for you. The async pipe subscribes to an Observable or Promise and unsubscribes automatically on component destroy to avoid potential memory leaks.
<ng-container *ngFor="let item of items$ | async">
// Handler
</ng-container>1.5 Avoid @HostListener
When you add a @HostListener to a component, it will work until the component is destroyed. So, if you only need few params from @HostListener during a short period of the host component’s lifecycle, then avoid add the @HostListener on the long-live component unless you have to. Instead, add the @HostListener to a temporary div and destroy it after you get what you want.
<div *ngIf="!hostListenerDataLoaded" hostListenerDirective (onValueChange)="yourDataHandler($event)">
</div>1.6 Use Pipe instead of function call inside template
When you use a function call with Angular structural directives inside your component, Angular won’t know what happen and therefore will call the function every time when the change detection runs even if the returned result will remain the exactly same (stackblitz). If you have to handle the data inside the template, consider using pipe.
Data processed by function call in template:
<!-- Bad -->
<div *ngIf="yourHandler(data)"></div>Data processed by pipe in template:
<!-- Good -->
<div *ngIf="data | yourHandlerPipe"></div>1.7 Virtual Scroll
Rendering a large lists of data items for browser is a big job and will probably also affect the whole page response time. And moreover, those extra rendered data items will probably end up not being looked at all. Instead rendering all items, you should consider using virtual scroll for your items display. Angular Material has provided for you.
<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items">{{item}}</div>
</cdk-virtual-scroll-viewport>1.8 Use ProvidedIn instead of Providers
When you want to have a dependency injection of a service, you can either declare the service with @Injectable() or use providers:[] at module or component level. This @Injectable()method is preferred because it enables tree-shaking of the service if nothing injects it. When you can use either providers or providedIn, always consider providedIn as priority.
// Bad
providers: [YourService],// Good
@Injectable({ providedIn: 'root' }) // or a SpecificModule
export class YourService { }1.9 Use import instead of require()
When you have a 3rd-party lib you will have to import into your util, it is always better to use import over require() if possible because import, the ES6 syntax, supports tree shaking when you compiling your code into minified javascript. More explanations can be found here.
Before ES6 modules, we had CommonJS modules which used the
require()syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code.
This dynamic nature of the CommonJS modules meant that tree shaking couldn’t be applied because it would be impossible to determine which modules will be needed before the code is actually run.
Wrapping Up
Performance optimisation is a big topic. Besides what we listed above related to JavaScript. There are many other optimisations in different perspectives, e.g. CSS, Backend or DevOps.
Other posts:
- Angular: Move 90% GET APIs Into Cacheable Shell Component
- Create a Custom Flutter Calendar Date Picker
- Create a Resizable and Draggable Angular Component
- Prerender Angular as static website and host it on AWS S3 with your custom domain
- Deploy Serverless Server-side-rendered Angular on AWS Lambda.
- Be Careful of Passing an Async Function as a Parameter





