> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance Optimization

> Best practices for optimizing Angular application performance including lazy loading, OnPush strategy, and trackBy functions

# Performance Optimization

Angular includes many optimizations out of the box, but as applications grow, you may need to fine-tune both how quickly your app loads and how responsive it feels during use. This guide covers the tools and techniques Angular provides to help you build fast applications.

## Loading Performance

Loading performance determines how quickly your application becomes visible and interactive. Slow loading directly impacts Core Web Vitals like Largest Contentful Paint (LCP) and Time to First Byte (TTFB).

### Lazy Loading Routes

Lazy loading defers loading route components until navigation, reducing the initial bundle size:

<CodeGroup>
  ```typescript app.routes.ts theme={null}
  import { Routes } from '@angular/router';

  export const routes: Routes = [
    {
      path: '',
      loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
    },
    {
      path: 'dashboard',
      loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
    },
    {
      path: 'admin',
      loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES)
    }
  ];
  ```

  ```typescript admin.routes.ts theme={null}
  import { Routes } from '@angular/router';

  export const ADMIN_ROUTES: Routes = [
    {
      path: 'users',
      loadComponent: () => import('./users/users.component').then(m => m.UsersComponent)
    },
    {
      path: 'settings',
      loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent)
    }
  ];
  ```
</CodeGroup>

<Info>
  **When to use**: Applications with multiple routes where not all are needed on initial load. This is one of the most effective ways to reduce initial bundle size.
</Info>

### Deferred Loading with @defer

The `@defer` block splits components into separate bundles that load on demand:

<CodeGroup>
  ```html Basic Defer theme={null}
  @defer {
    <large-component />
  } @placeholder {
    <p>Loading content...</p>
  }
  ```

  ```html Defer with Triggers theme={null}
  @defer (on viewport) {
    <heavy-chart-component />
  } @loading (minimum 1s) {
    <img alt="loading..." src="loading.gif" />
  } @placeholder {
    <div class="chart-skeleton"></div>
  } @error {
    <p>Failed to load chart</p>
  }
  ```

  ```html Defer on Interaction theme={null}
  @defer (on interaction) {
    <comment-section />
  } @placeholder {
    <button>Load Comments</button>
  }
  ```
</CodeGroup>

<Tabs>
  <Tab title="Viewport Trigger">
    ```html theme={null}
    @defer (on viewport) {
      <reviews-list />
    }
    ```

    Loads when the placeholder scrolls into view
  </Tab>

  <Tab title="Interaction Trigger">
    ```html theme={null}
    @defer (on interaction) {
      <advanced-filters />
    }
    ```

    Loads when user clicks or focuses the placeholder
  </Tab>

  <Tab title="Idle Trigger">
    ```html theme={null}
    @defer (on idle) {
      <analytics-widget />
    }
    ```

    Loads when the browser is idle (default behavior)
  </Tab>

  <Tab title="Timer Trigger">
    ```html theme={null}
    @defer (on timer(3s)) {
      <promo-banner />
    }
    ```

    Loads after a specified delay
  </Tab>
</Tabs>

<Note>
  **When to use**: Components not visible on initial render, heavy third-party libraries, below-the-fold content.
</Note>

### Prefetching Deferred Content

You can prefetch deferred content before it's needed:

```html theme={null}
@defer (on viewport; prefetch on idle) {
  <product-recommendations />
} @placeholder {
  <div class="recommendations-skeleton"></div>
}
```

<CardGroup cols={2}>
  <Card title="Immediate Prefetch" icon="bolt">
    ```html theme={null}
    @defer (prefetch on immediate)
    ```

    Starts prefetching immediately
  </Card>

  <Card title="Idle Prefetch" icon="clock">
    ```html theme={null}
    @defer (prefetch on idle)
    ```

    Prefetches when browser is idle
  </Card>

  <Card title="Hover Prefetch" icon="hand-pointer">
    ```html theme={null}
    @defer (prefetch on hover)
    ```

    Prefetches on hover over placeholder
  </Card>

  <Card title="Timer Prefetch" icon="stopwatch">
    ```html theme={null}
    @defer (prefetch on timer(2s))
    ```

    Prefetches after specified time
  </Card>
</CardGroup>

## Runtime Performance

Runtime performance determines how responsive your application feels after it loads. Angular's change detection system keeps the DOM in sync with your data, and optimizing how and when it runs is the primary lever for improving runtime performance.

### OnPush Change Detection

OnPush change detection instructs Angular to run change detection for a component subtree **only** when:

* The root component of the subtree receives new inputs (compared with `==`)
* Angular handles an event in the subtree's root component or any of its children

<CodeGroup>
  ```typescript user-profile.component.ts theme={null}
  import { Component, ChangeDetectionStrategy, input, output } from '@angular/core';
  import { User } from './user.model';

  @Component({
    selector: 'app-user-profile',
    templateUrl: './user-profile.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush  // Enable OnPush
  })
  export class UserProfileComponent {
    readonly user = input.required<User>();
    readonly userUpdated = output<User>();
    
    updateUser(updates: Partial<User>) {
      // This will trigger change detection for this component
      this.userUpdated.emit({ ...this.user(), ...updates });
    }
  }
  ```

  ```typescript user-list.component.ts theme={null}
  import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
  import { User } from './user.model';
  import { UserProfileComponent } from './user-profile.component';

  @Component({
    selector: 'app-user-list',
    template: `
      @for (user of users(); track user.id) {
        <app-user-profile 
          [user]="user" 
          (userUpdated)="onUserUpdated($event)" />
      }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
  })
  export class UserListComponent {
    users = signal<User[]>([]);
    
    onUserUpdated(user: User) {
      // Create new array reference to trigger change detection
      this.users.update(users => 
        users.map(u => u.id === user.id ? user : u)
      );
    }
  }
  ```
</CodeGroup>

<Warning>
  **Important**: When using OnPush, you must ensure that input references change when the data changes. Modifying objects in-place won't trigger change detection.
</Warning>

### Understanding OnPush Scenarios

<Tabs>
  <Tab title="Event in Default Component">
    If Angular handles an event within a component **without** OnPush, the framework executes change detection on the entire component tree, skipping OnPush subtrees that haven't received new inputs.

    ```mermaid theme={null}
    graph TD;
        app[AppComponent] --- header[HeaderComponent];
        app --- main["MainComponent (OnPush)"];
        header --- search[SearchComponent];
        main --- login["LoginComponent (OnPush)"];
        main --- details[DetailsComponent];
        event>Event] --- search

    class app checked
    class header checked
    class search checked
    ```
  </Tab>

  <Tab title="Event in OnPush Component">
    If Angular handles an event within a component **with** OnPush, the framework runs change detection in that component and its ancestors, ignoring other OnPush subtrees.

    ```typescript theme={null}
    @Component({
      selector: 'app-main',
      changeDetection: ChangeDetectionStrategy.OnPush,
      template: `
        <button (click)="handleClick()">Click me</button>
        <app-login />
        <app-details />
      `
    })
    export class MainComponent {
      handleClick() {
        // This triggers change detection for MainComponent and ancestors
        // but not for LoginComponent (OnPush sibling)
      }
    }
    ```
  </Tab>

  <Tab title="New Inputs to OnPush">
    Angular runs change detection in a child component with OnPush when setting an input property as a result of a template binding.

    ```typescript theme={null}
    @Component({
      selector: 'app-parent',
      template: `<app-child [data]="currentData()" />`
    })
    export class ParentComponent {
      currentData = signal({ id: 1, name: 'Test' });
      
      updateData() {
        // This creates a new reference, triggering change detection in child
        this.currentData.set({ id: 1, name: 'Updated' });
      }
    }
    ```
  </Tab>
</Tabs>

### Using trackBy for Performance

When rendering lists, use `track` to help Angular identify which items have changed:

<CodeGroup>
  ```typescript Without trackBy (Avoid) theme={null}
  @Component({
    selector: 'app-product-list',
    template: `
      @for (product of products(); track $index) {
        <div class="product">
          <h3>{{ product.name }}</h3>
          <p>{{ product.price }}</p>
        </div>
      }
    `
  })
  export class ProductListComponent {
    products = signal<Product[]>([]);
  }
  ```

  ```typescript With trackBy (Preferred) theme={null}
  @Component({
    selector: 'app-product-list',
    template: `
      @for (product of products(); track product.id) {
        <div class="product">
          <h3>{{ product.name }}</h3>
          <p>{{ product.price }}</p>
        </div>
      }
    `
  })
  export class ProductListComponent {
    products = signal<Product[]>([]);
  }
  ```

  ```typescript Custom trackBy Function theme={null}
  @Component({
    selector: 'app-user-list',
    template: `
      @for (user of users(); track trackByUserId($index, user)) {
        <div class="user-card">
          <h3>{{ user.name }}</h3>
          <p>{{ user.email }}</p>
        </div>
      }
    `
  })
  export class UserListComponent {
    users = signal<User[]>([]);
    
    // Custom track function for complex scenarios
    protected trackByUserId(index: number, user: User): string {
      return user.id;
    }
  }
  ```
</CodeGroup>

<Info>
  **Why use trackBy?** Without proper tracking, Angular recreates DOM elements when the array reference changes, even if the items are the same. This causes expensive DOM operations and loses component state.
</Info>

### Optimizing Computed Values

Use computed signals for expensive calculations to avoid recalculating on every change detection:

<CodeGroup>
  ```typescript Good - Using Computed theme={null}
  import { Component, signal, computed } from '@angular/core';

  @Component({
    selector: 'app-dashboard',
    template: `
      <div class="stats">
        <p>Total Revenue: {{ totalRevenue() }}</p>
        <p>Average Order: {{ averageOrder() }}</p>
        <p>Top Product: {{ topProduct() }}</p>
      </div>
    `
  })
  export class DashboardComponent {
    orders = signal<Order[]>([]);
    
    // Computed values are cached and only recalculated when dependencies change
    protected totalRevenue = computed(() => 
      this.orders().reduce((sum, order) => sum + order.total, 0)
    );
    
    protected averageOrder = computed(() => {
      const orders = this.orders();
      return orders.length ? this.totalRevenue() / orders.length : 0;
    });
    
    protected topProduct = computed(() => {
      const productCounts = new Map<string, number>();
      this.orders().forEach(order => {
        order.items.forEach(item => {
          productCounts.set(item.product, (productCounts.get(item.product) || 0) + 1);
        });
      });
      return Array.from(productCounts.entries())
        .sort((a, b) => b[1] - a[1])[0]?.[0] || 'None';
    });
  }
  ```

  ```typescript Avoid - Recalculating Every Time theme={null}
  import { Component, signal } from '@angular/core';

  @Component({
    selector: 'app-dashboard',
    template: `
      <div class="stats">
        <p>Total Revenue: {{ getTotalRevenue() }}</p>
        <p>Average Order: {{ getAverageOrder() }}</p>
        <p>Top Product: {{ getTopProduct() }}</p>
      </div>
    `
  })
  export class DashboardComponent {
    orders = signal<Order[]>([]);
    
    // These methods run on every change detection cycle
    getTotalRevenue(): number {
      return this.orders().reduce((sum, order) => sum + order.total, 0);
    }
    
    getAverageOrder(): number {
      const orders = this.orders();
      return orders.length ? this.getTotalRevenue() / orders.length : 0;
    }
    
    getTopProduct(): string {
      const productCounts = new Map<string, number>();
      this.orders().forEach(order => {
        order.items.forEach(item => {
          productCounts.set(item.product, (productCounts.get(item.product) || 0) + 1);
        });
      });
      return Array.from(productCounts.entries())
        .sort((a, b) => b[1] - a[1])[0]?.[0] || 'None';
    }
  }
  ```
</CodeGroup>

### Zoneless Change Detection

Zoneless change detection removes ZoneJS overhead and triggers change detection only when signals or events indicate a change:

```typescript app.config.ts theme={null}
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection(),  // Enable zoneless
    provideRouter(routes)
  ]
};
```

<Info>
  **When to use**: New applications (default in Angular v21+), or existing applications ready to migrate. Zoneless mode requires using signals for reactive state management.
</Info>

## Measuring Performance

### Chrome DevTools Profiling

<Steps>
  <Step title="Open DevTools Performance Panel">
    Press `F12` and navigate to the Performance tab
  </Step>

  <Step title="Start Recording">
    Click the record button and interact with your application
  </Step>

  <Step title="Analyze Angular Track">
    Look for the "Angular" track in the flame chart to see:

    * Component rendering times
    * Change detection cycles
    * Lifecycle hook execution
  </Step>

  <Step title="Identify Bottlenecks">
    Look for:

    * Long-running functions
    * Excessive change detection cycles
    * Slow component rendering
  </Step>
</Steps>

### Angular DevTools

The Angular DevTools browser extension provides:

<CardGroup cols={2}>
  <Card title="Component Inspector" icon="microscope">
    Inspect component tree, view properties, and modify state in real-time
  </Card>

  <Card title="Profiler" icon="chart-line">
    Visualize change detection cycles and identify performance bottlenecks
  </Card>
</CardGroup>

## Performance Checklist

<AccordionGroup>
  <Accordion title="Loading Performance">
    * [ ] Implement lazy loading for routes not needed on initial load
    * [ ] Use `@defer` blocks for heavy components below the fold
    * [ ] Optimize images with NgOptimizedImage directive
    * [ ] Consider server-side rendering for content-heavy pages
    * [ ] Enable production mode for deployments
  </Accordion>

  <Accordion title="Runtime Performance">
    * [ ] Use OnPush change detection for presentational components
    * [ ] Always use `track` with unique identifiers in `@for` loops
    * [ ] Use computed signals for derived values
    * [ ] Avoid complex logic in templates
    * [ ] Consider zoneless change detection for new applications
  </Accordion>

  <Accordion title="Code Organization">
    * [ ] Keep components focused on presentation
    * [ ] Move business logic to services
    * [ ] Use pure pipes for transformations
    * [ ] Avoid subscriptions in components (use async pipe or signals)
    * [ ] Unsubscribe from observables to prevent memory leaks
  </Accordion>

  <Accordion title="Bundle Size">
    * [ ] Analyze bundle size with `ng build --stats-json`
    * [ ] Remove unused dependencies
    * [ ] Use tree-shakable providers
    * [ ] Lazy load feature modules
    * [ ] Use dynamic imports for large libraries
  </Accordion>
</AccordionGroup>

## What to Optimize First

Profile your application first using Chrome DevTools to identify specific bottlenecks. As a general starting point:

<CardGroup cols={2}>
  <Card title="Slow Initial Load" icon="hourglass-start">
    * Use `@defer` to split large components
    * Implement lazy loading for routes
    * Enable server-side rendering
    * Optimize images with NgOptimizedImage
  </Card>

  <Card title="Slow Interactions" icon="hand-pointer">
    * Enable zoneless change detection
    * Look for slow computations in templates
    * Use OnPush change detection strategy
    * Add trackBy functions to lists
  </Card>
</CardGroup>

<Note>
  **Pro Tip**: Don't optimize prematurely. Profile first, identify actual bottlenecks, then optimize those specific areas.
</Note>

<Card title="Next Steps" icon="arrow-right" href="/best-practices/security">
  Learn about security best practices in Angular applications
</Card>
