There are multiple ways we can optimize performance of an Angular application.
Use Ahead-of-Time (AOT) Compilation
Angular uses Just-In-Time compilation for development, but you should use AOT compilation for production.
Why?
Convert Angular HTML and TypeScript code into JS code making use of AOT compilation. This provides faster rendering and improved performance.
How?
ng build –configuration production
This enables AOT, minification, and other optimizations.
Lazy Loading Modules
Instead of loading all modules at once, load them on demand using Angular’s Router.
Why?
Improve faster load times by reducing the initial bundle size:
Example:
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.module').then((m) => m.AdminModule),
},
];
Now, AdminModule is only loaded when the user navigates to /admin.
Tree Shaking and Removing Unused Code
Ensure that unused services, modules, or third-party libraries are not bundled into your production build.
Why?
Smaller bundles = faster downloads + parsing + rendering.
How?
Use the –configuration production flag.
Avoid using barrel files that re-export everything (index.ts) blindly.
Import only the functions you need from large libraries (e.g., lodash-es instead of lodash).
Change Detection Strategy
Implement ChangeDetectionStrategy.OnPush using component switching without relying on external state.
Why?
By default, Angular checks every component in the tree on every change. OnPush limits checks to components whose inputs have changed.
Example:
@Component({
selector: 'app-optimized',
templateUrl: './optimized.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OptimizedComponent {
@Input() data: any;
}
Use TrackBy with ngFor
When rendering lists, Angular re-renders items unless you help it identify which items changed using trackBy.
Why?
Prevents unnecessary DOM manipulations.
Example:
<div *ngFor="let user of users; trackBy: trackById"> {{ user.name }}
</div>
trackById(index: number, user: User): number { return user.id;
}
Optimize Third-Party Libraries
Use smaller, tree-shakable versions of libraries.
Avoid full imports.
Bad:
import * as _ from 'lodash';
Good:
import debounce from ‘lodash-es/debounce’; or switch to native equivalents where possible.
Bundle Optimization
Make use of Angular CLI command source-map-explorer or webpack-bundle-analyzer to analyze bundle size.
Install:
npm install -g source-map-explorer
Analyze:
ng build --configuration production
npx source-map-explorer dist/[your-app-name]/*.js
Identify and optimize libraries or codebases for optimization or lazy-loading implementation.
Code Splitting
Break down monolithic Angular codebase into multiple smaller bundles.
Best Practices in Angular code splitting:
- Use lazy loading as shown earlier
- Dynamically import features
Avoid Memory Leaks
Unsubscribe from Observables, especially in ngOnDestroy, or use takeUntil and AsyncPipe.
Example with AsyncPipe:
<div *ngIf="data$ | async as data">{{ data }}</div>
Example with takeUntil:
private destroy$ = new Subject();
this.myService.getData()
.pipe(takeUntil(this.destroy$))
.subscribe();
ngOnDestroy() {
this.destroy$.next();this.destroy$.complete();}