← Back to Chapters

Best Practices & Optimization

⚡ Best Practices & Optimization

? Quick Overview

Following best practices and optimizing your Angular applications improves performance, maintainability, and scalability. These practices cover coding standards, change detection, lazy loading, and efficient module design.

? Goal: Fast, scalable, maintainable Angular apps

? Key Concepts

  • Change Detection Strategy: Use OnPush where possible to reduce unnecessary checks.
  • Lazy Loading: Load feature modules and routes only when needed to speed up initial load time.
  • trackBy in *ngFor: Optimize DOM rendering for large or frequently changing lists.
  • Standalone Components (Angular 15+): Reduce NgModule boilerplate and simplify architecture.
  • Services for Shared Logic: Centralize shared logic and state in services instead of bloating components.
  • RxJS & async Pipe: Use async pipe to manage subscriptions automatically.
  • Feature Modules: Structure your app by features for better scalability and team collaboration.

? Syntax & Theory

? OnPush Change Detection

By default, Angular runs change detection for many events (like clicks, HTTP responses, timers, etc.). With ChangeDetectionStrategy.OnPush, Angular only checks a component when:

  • An @Input() reference changes.
  • An observable bound with async pipe emits a new value.
  • You manually trigger detection (e.g., using ChangeDetectorRef).

? Lazy Loading Modules

Lazy loading lets you split your app into smaller bundles and load feature modules only when the user navigates to them. This improves the first load time and overall performance, especially in large applications.

? Optimizing Lists with trackBy

When rendering lists with *ngFor, Angular re-renders items when the array changes. Using trackBy helps Angular reuse DOM elements based on a unique identifier (like id), which significantly improves performance for large lists.

? Code Example: OnPush Change Detection

? View Code Example: Child Component with OnPush
// Child component using OnPush change detection
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: '<p>{{ data }}</p>',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data!: string;
}

? Explanation

  • ChangeDetectionStrategy.OnPush tells Angular to run change detection only when input references change.
  • The ChildComponent will not be re-checked on every global event, improving performance.
  • This pattern works best when your inputs are immutable (e.g., using spread operators or immutable data structures).

⚙️ Code Example: Lazy Loaded Feature Module

? View Code Example: Lazy Loading Routes
// App routing module with a lazy loaded Admin feature module
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

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

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

? What This Achieves

  • The AdminModule is only downloaded when the user navigates to /admin.
  • This reduces the size of the initial bundle users have to download.
  • Ideal for heavy sections such as admin dashboards, reports, or rarely used features.

✅ Tips & Best Practices

  • Structure your app using feature modules for better maintainability and scalability.
  • Keep components small and focused; move reusable or complex logic into services.
  • Monitor bundle size and enable tree-shaking to remove unused code.
  • Use Ahead-of-Time (AOT) compilation and production build optimizations for deployment.
  • Prefer the async pipe over manual subscribe() to avoid memory leaks.
  • Adopt standalone components in newer Angular versions to reduce boilerplate.

? Common Use Cases

  • Large enterprise Angular applications with many features and modules.
  • Dashboards and analytics apps that render large, dynamic lists or tables.
  • Applications accessed on low-bandwidth networks where bundle size matters.
  • Teams with multiple developers working on separate feature modules.

? Try It Yourself / Practice Tasks

  • Create a feature module (e.g., ReportsModule) and implement lazy loading with routing.
  • Refactor an existing component to use ChangeDetectionStrategy.OnPush and the async pipe for observable data.
  • Implement trackBy in a list component to optimize DOM rendering for large datasets, using a unique id for each item.
  • Identify one area of your current Angular app where a feature module or standalone component could simplify the structure, and refactor it.