Angular apps can slow down quietly as they grow, more components, more data, more subscriptions nobody remembers to clean up. None of the fixes below are advanced tricks, just habits that make a real difference once you apply them consistently.
1. Use OnPush change detection
By default, Angular checks every component on every change detection cycle, even ones that have not actually changed. Switching to OnPush tells Angular to only re-check a component when its inputs change or an event fires inside it.
@Component({
selector: "app-user-card",
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "./user-card.component.html",
})
export class UserCardComponent {
@Input() user: User;
}
This alone can meaningfully cut down unnecessary checks in larger apps, especially ones with deep component trees.
2. Unsubscribe from observables properly
Forgetting to unsubscribe is one of the most common sources of memory leaks in Angular apps. Subscriptions that outlive their component keep doing work in the background long after the component is gone.
private destroy$ = new Subject<void>();
ngOnInit() {
this.userService.getUser()
.pipe(takeUntil(this.destroy$))
.subscribe(user => this.user = user);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
The async pipe in templates is even better when possible, it subscribes and unsubscribes automatically.
<div *ngIf="user$ | async as user">
{{ user.name }}
</div>
3. Use trackBy with ngFor
Without trackBy, Angular re-renders the entire list whenever the underlying array reference changes, even if most items stayed the same. trackBy tells Angular how to identify individual items, so it only updates what actually changed.
trackByUserId(index: number, user: User): number {
return user.id;
}
<div *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }}
</div>
Small change, noticeable difference on any list that updates frequently.
4. Lazy load feature modules
Loading the entire app upfront means users wait for code they might not even use in that session. Lazy loading splits your app into chunks that load only when needed.
const routes: Routes = [
{
path: "admin",
loadChildren: () =>
import("./admin/admin.module").then((m) => m.AdminModule),
},
];
This keeps your initial bundle smaller, which directly improves first load time.
5. Avoid function calls in templates
Calling a function directly in a template runs it on every single change detection cycle, not just when its inputs actually change.
<!-- Runs on every change detection cycle -->
<div>{{ calculateTotal() }}</div>
<!-- Calculated once, only recalculates when dependencies change -->
<div>{{ total }}</div>
If a value needs computing, calculate it once in the component class (or with a pure pipe) instead of calling a function straight from the template.
6. Use pure pipes for transformations
Pipes marked as pure only re-run when their input reference changes, unlike template function calls, which run constantly.
@Pipe({
name: "formatCurrency",
pure: true,
})
export class FormatCurrencyPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}
<div>{{ price | formatCurrency }}</div>
Same result as a template function, far less repeated work.
7. Virtual scroll for long lists
Rendering hundreds or thousands of DOM elements at once, even off screen, is expensive. Angular CDK's virtual scroll only renders what is actually visible in the viewport.
html
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items">{{ item.name }}</div>
</cdk-virtual-scroll-viewport>
Makes a real difference on any list that can realistically grow large, like search results or activity feeds.
What actually matters most
If I had to pick just two to start with: OnPush change detection and properly unsubscribing from observables. Those two alone catch a large chunk of the performance issues I have run into in real Angular apps, the rest are worth adding as the app grows.
Curious what other practices people rely on, especially anything specific to newer Angular features like signals, that changes some of this.