It is important to tackle performance challenges to get performant enterprise Angular applications. As applications scale, multiple factors affect its performance. From poor component design to inefficient change detection, let’s break down the common challenges and their solutions with in-depth explanations and examples.

Common Performance Bottlenecks in Angular

  • Inefficient Change Detection
  • Large Bundle Size
  • Memory Leaks
  • Heavy Initial Load
  • Too Many DOM Updates
  • Excessive API Calls
  • Unoptimized Angular Modules
  • Inefficient RxJS Handling

Techniques to Handle Performance Bottlenecks

Use OnPush Change Detection Strategy

Angular uses Default Change Detection by default, which checks the entire component tree when any data changes. In large apps, this can be costly.

Solution: Use ChangeDetectionStrategy.OnPush

This tells Angular to check the component only when inputs change or an observable emits new data.

Example:

import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
@Component({ selector: 'app-user-card', template: `<p>{{ user.name }}</p>`, changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent { @Input() user: any;
}

Use immutability (e.g., user = { …user, name: ‘New Name’ }) so Angular detects the change.

Lazy Load Modules and Components

Load code only when needed instead of bundling everything in the initial load.

Solution: Use Angular’s lazy loading with routing or loadComponent.

Example (Routing):

const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
];

Example (Standalone Component):

import { loadComponent } from '@angular/core';
{ path: 'product', loadComponent: () => import('./product/product.component').then(m => m.ProductComponent)
}

Tree-Shaking and Smaller Bundle Size

Large bundle sizes increase load time. Unused code should be removed.

Solution:

  • Use –prod builds: ng build –configuration production
  • Remove unused dependencies
  • Prefer standalone components in Angular 15+ for more tree-shaking

Use trackBy with *ngFor

Without trackBy, Angular re-renders the whole list when anything changes.
Example:

<div *ngFor="let user of users; trackBy: trackById"> {{ user.name }}
</div>trackById(index: number, user: any) { return user.id;
}

Avoid Memory Leaks (unsubscribe properly)

Using observables without unsubscribing can lead to memory leaks.

Solution:

Use async pipe

Or manage subscriptions using takeUntil and Subject

Example with takeUntil:

private destroy$ = new Subject<void>();
ngOnInit() { this.apiService.getData() .pipe(takeUntil(this.destroy$)) .subscribe(data => this.data = data);
}
ngOnDestroy() { this.destroy$.next(); this.destroy$.complete();
}

Optimize Third-Party Libraries

Don’t import whole libraries—import only what you need.

Bad:

import * as _ from ‘lodash’;

Good:

import cloneDeep from ‘lodash/cloneDeep’;

Use Web Workers for Heavy Computation

Move CPU-intensive tasks off the main thread using Web Workers.

Example:

ng generate web-worker heavy-task

    In the worker:

    addEventListener('message', ({ data }) => { const result = heavyCalculation(data); postMessage(result);
    });

    In component:

    const worker = new Worker(new URL('./heavy-task.worker', import.meta.url));
    worker.postMessage(data);
    worker.onmessage = ({ data }) => { this.result = data;
    };

    Use SSR or Pre-rendering for Faster FCP

    Angular Universal allows server-side rendering (SSR), reducing Time to First Paint.

    ng add @nguniversal/express-engine

    Use Signals (Angular 17+) for Fine-grained Reactivity

    Signals optimize change detection by precisely tracking reactive dependencies.

    Example:

    import { signal } from '@angular/core';
    const count = signal(0);
    count.update(n => n + 1);

    This is more efficient than @Input or @Output for local state updates.

    Profile and Debug

    Use Chrome DevTools, Angular DevTools, and ng.profiler.timeChangeDetection() to find slow parts of your app.

    Real-World Scenario

    Problem:

    A dashboard component renders 500 cards, each showing real-time data. The app slows down as more data updates arrive.

    Optimization Steps:

    • Use ChangeDetectionStrategy.OnPush in card components.
    • Use trackBy in *ngFor.
    • Replace frequent polling with WebSocket and RxJS distinctUntilChanged.
    • Move cards into a lazily loaded module.
    • Use requestAnimationFrame to batch DOM updates.

    Preload card images or icons using a service.