# Performance Optimization

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: Application Quality
Source: https://www.devtutorials.ai/learn/angular/quality/performance-optimization

> Making apps fast: measuring performance, lazy loading, bundle optimization, change detection with Default and OnPush, track in @for, trackBy in *ngFor, and rendering best practices.

## Why Performance Matters

**Performance** is how fast your app loads and how fast it reacts to the user. A slow app loses users:

- Users leave a page that takes more than a few seconds to show content.
- Slow reactions to clicks and typing make an app feel broken.
- Many users have slow phones and slow mobile networks, not a fast developer computer.
- Search engines rank fast pages higher.

Angular apps have two kinds of performance:

| Kind | Question | Main tools in this lesson |
|---|---|---|
| **Load performance** | How fast does the first page appear? | Lazy loading, bundle optimization |
| **Runtime performance** | How fast does the app react after it has loaded? | Change detection, `OnPush`, `track` |

### Real-World Example

An e-commerce admin panel shows a table of 2,000 orders. Every click on any button makes Angular check all 2,000 rows again, and the page freezes for half a second.

The team switches the row component to `OnPush` and adds `track order.id` to the list. Now a click checks only the rows that changed, and the page reacts at once.

## Performance Monitoring Basics

**Measure first, then optimize.** Without measurements, you cannot know which part is slow, or if your change helped.

Tools that are already in the browser and in Angular:

- **`ng build` output:** the size of each JavaScript file. Look at it after every bigger change.
- **Lighthouse** (in Chrome's developer tools): gives the page a performance score and lists problems, such as large files.
- **The Performance tab** of the developer tools: records what the browser does during a click, and shows slow functions.
- **Angular DevTools Profiler:** shows each change detection run, how long it took and which components it checked.
- **`console.time()`** and **`performance.now()`** measure how long your own code takes.

```angular runnable title="Measuring the time of a function"
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="sortOrders()">Sort 200,000 orders</button>
    <p>{{ status }}</p>
  `,
})
export class App {
  status = 'Open the browser console (F12), then click the button.';

  sortOrders() {
    const orders = Array.from({ length: 200000 }, () => Math.random());

    const start = performance.now();
    orders.sort((a, b) => a - b);
    const ms = performance.now() - start;

    console.log(`Sorting took ${ms.toFixed(1)} ms`);
    this.status = `Sorted ${orders.length} orders. The time is in the console.`;
  }
}
```

`performance.now()` returns the time in milliseconds with decimals. The difference between two calls is the time the code needed.

## Lazy Loading

You learned **lazy loading** in the Routing lesson: a page's code is downloaded only when the user opens it. This is the most effective way to make the first load faster.

<figure className="ngc-fig">
<svg viewBox="0 0 640 210" role="img" aria-label="main.js downloads first; reports.js and admin.js download only when their routes open.">
<defs><marker id="ngc-p2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-step ngc-t1"><g className="ngc-node ngc-solid c-blue"><rect x="10" y="60" width="170" height="70" rx="9" /><text x="95.0" y="92" textAnchor="middle" style={{fontSize:"14px"}}>main.js</text><text className="ngc-sub" x="95.0" y="110" textAnchor="middle">first download</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-orange"><rect x="440" y="20" width="190" height="60" rx="9" /><text x="535.0" y="47" textAnchor="middle" style={{fontSize:"14px"}}>reports.js</text><text className="ngc-sub" x="535.0" y="65" textAnchor="middle">when /reports opens</text></g></g><path className="ngc-link ngc-flow c-orange" d="M182 95 C 300 95, 320 50, 436 50" style={{fill:"none"}} /><circle className="ngc-packet c-orange" r="5"><animateMotion dur="2.4s" repeatCount="indefinite" path="M182 95 C 300 95, 320 50, 436 50" /></circle><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="440" y="110" width="190" height="60" rx="9" /><text x="535.0" y="137" textAnchor="middle" style={{fontSize:"14px"}}>admin.js</text><text className="ngc-sub" x="535.0" y="155" textAnchor="middle">when /admin opens</text></g></g><path className="ngc-link ngc-flow c-orange" d="M182 95 C 300 95, 320 140, 436 140" style={{fill:"none"}} /><circle className="ngc-packet c-orange" r="5"><animateMotion dur="2.4s" repeatCount="indefinite" path="M182 95 C 300 95, 320 140, 436 140" /></circle><text className="ngc-note" x="310" y="196" textAnchor="middle">Lazy chunks are downloaded only when the user needs them.</text>
</svg>
<figcaption>Lazy loading splits the app into a small first download and later chunks.</figcaption>
</figure>

```ts
export const routes: Routes = [
  { path: '', component: Home },                                   // in the first download
  { path: 'reports', loadComponent: () => import('./reports/reports').then(m => m.Reports) },
  { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES) },
];
```

After `ng build`, the output lists the lazy files separately under **Lazy chunk files**. A **chunk** is one JavaScript file of the build.

- Lazy-load large pages and pages that few users open: reports, admin, settings.
- Keep the first page and the most used pages in the first download.

Lazy loading and bundle size matter only for the **first** visit and for opening new pages. Change detection matters for **every** click. Check both kinds of performance.

## Bundle Optimization

A **bundle** is the set of JavaScript files that the browser downloads. Smaller bundles load faster.

- **Build for production.** `ng build` uses the production configuration by default. It removes unused code (**tree-shaking**), shortens names (**minification**) and removes development checks.
- **Use budgets.** In `angular.json`, `budgets` warn you, or stop the build, when the bundle grows too large:

```json
"budgets": [
  { "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" }
]
```

- **Import only what you use.** Import single functions, such as `import { map } from 'rxjs'`, not whole libraries.
- **Check new libraries before you add them.** A large library for one small feature makes every user wait.
- **Optimize images.** Use the right size and modern formats. Images are often larger than all the JavaScript.

## Angular Change Detection

**Change detection** is how Angular keeps the screen in sync with the data. During a change detection run, Angular checks the templates of components, compares each value with the last one, and updates the elements whose values changed.

A run starts after the rendering triggers you learned in the Client-Side Rendering lesson:

- An event in a template, such as `(click)`
- A signal that a template reads gets a new value
- The `async` pipe receives a new value

<figure className="ngc-fig">
<svg viewBox="0 0 640 208" role="img" aria-label="A click in App starts change detection: the Default OrderList is checked, while the OnPush Header and Footer are skipped.">
<defs><marker id="ngc-q2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-step ngc-t1"><g className="ngc-node ngc-solid c-blue"><rect x="230" y="8" width="180" height="52" rx="9" /><text x="320.0" y="31" textAnchor="middle" style={{fontSize:"14px"}}>App</text><text className="ngc-sub" x="320.0" y="49" textAnchor="middle">click happens here</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-orange"><rect x="20" y="110" width="180" height="56" rx="9" /><text x="110.0" y="135" textAnchor="middle" style={{fontSize:"14px"}}>OrderList</text><text className="ngc-sub" x="110.0" y="153" textAnchor="middle">Default: checked</text></g></g><line className="ngc-link ngc-flow c-slate" x1="320" y1="62" x2="110" y2="108" markerEnd="url(#ngc-q2)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-green"><rect x="230" y="110" width="180" height="56" rx="9" /><text x="320.0" y="135" textAnchor="middle" style={{fontSize:"14px"}}>Header</text><text className="ngc-sub" x="320.0" y="153" textAnchor="middle">OnPush: skipped</text></g></g><line className="ngc-link ngc-flow c-slate" x1="320" y1="62" x2="320" y2="108" markerEnd="url(#ngc-q2)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="440" y="110" width="180" height="56" rx="9" /><text x="530.0" y="135" textAnchor="middle" style={{fontSize:"14px"}}>Footer</text><text className="ngc-sub" x="530.0" y="153" textAnchor="middle">OnPush: skipped</text></g></g><line className="ngc-link ngc-flow c-slate" x1="320" y1="62" x2="530" y2="108" markerEnd="url(#ngc-q2)" /><text className="ngc-note" x="320" y="196" textAnchor="middle">Default components are checked on every run. OnPush components only when they have a reason.</text>
</svg>
<figcaption>Change detection with Default and OnPush components.</figcaption>
</figure>

Each check is fast, but an app with hundreds of components and long lists can do a lot of unnecessary work. The rest of this lesson shows how to check less.

## Change Detection Strategies

Each component has a **change detection strategy**. It decides **when** Angular checks that component. There are two:

| Strategy | When Angular checks the component |
|---|---|
| `Default` | On every change detection run |
| `OnPush` | Only when the component has a reason: a new input value, an event inside it, or a change in a signal or `async` pipe that its template reads |

Set it with the `changeDetection` property:

```ts
import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
  selector: 'app-order-row',
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrderRow {}
```

## Default Change Detection

With the **Default** strategy, Angular checks the component on every run, even if nothing in it changed. This is simple and always correct, but it does unnecessary work.

In the next example, `checks()` is a method called by the template, so it runs every time Angular checks the component. Click the buttons, then click **Show check counts**.

```angular runnable title="Default and OnPush components"
import { ChangeDetectionStrategy, Component } from '@angular/core';

export const counts = { defaultPanel: 0, onPushPanel: 0 };

@Component({
  selector: 'app-default-panel',
  template: `<p>Default panel {{ checks() }}</p>`,
})
export class DefaultPanel {
  checks() {
    counts.defaultPanel++;
    return '';
  }
}

@Component({
  selector: 'app-onpush-panel',
  template: `<p>OnPush panel {{ checks() }}</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OnPushPanel {
  checks() {
    counts.onPushPanel++;
    return '';
  }
}

@Component({
  selector: 'app-root',
  imports: [DefaultPanel, OnPushPanel],
  template: `
    <button (click)="clicks = clicks + 1">Click in the parent ({{ clicks }})</button>
    <app-default-panel />
    <app-onpush-panel />
    <button (click)="report = 'Default checked ' + counts.defaultPanel + ' times, OnPush checked ' + counts.onPushPanel + ' times.'">
      Show check counts
    </button>
    <p>{{ report }}</p>
  `,
})
export class App {
  clicks = 0;
  report = '';
  counts = counts;
}
```

After five clicks in the parent, the Default panel was checked after every click. The OnPush panel was checked only when it was created, because nothing in it changed.

## `OnPush` Change Detection Strategy

With **`OnPush`**, Angular skips the component unless one of these happens:

<figure className="ngc-fig">
<svg viewBox="0 0 640 234" role="img" aria-label="Changing a property of the same object is skipped by OnPush; passing a new object updates the child.">
<defs><marker id="ngc-p3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><text className="ngc-title c-red" x="160" y="22" textAnchor="middle">customer.name = "Ravi"</text><text className="ngc-title c-green" x="480" y="22" textAnchor="middle">&#123; ...customer, name: "Meena" &#125;</text><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-red"><rect x="30" y="36" width="260" height="46" rx="9" /><text x="160.0" y="56" textAnchor="middle" style={{fontSize:"13px"}}>Same object</text><text className="ngc-sub" x="160.0" y="74" textAnchor="middle">reference unchanged</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="83" x2="160" y2="99" markerEnd="url(#ngc-p3)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-red"><rect x="30" y="100" width="260" height="46" rx="9" /><text x="160.0" y="120" textAnchor="middle" style={{fontSize:"13px"}}>OnPush: no new input</text><text className="ngc-sub" x="160.0" y="138" textAnchor="middle">child is skipped</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="147" x2="160" y2="163" markerEnd="url(#ngc-p3)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-red"><rect x="30" y="164" width="260" height="46" rx="9" /><text x="160.0" y="192" textAnchor="middle" style={{fontSize:"13px"}}>Child shows old name</text></g></g><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-green"><rect x="350" y="36" width="260" height="46" rx="9" /><text x="480.0" y="56" textAnchor="middle" style={{fontSize:"13px"}}>New object</text><text className="ngc-sub" x="480.0" y="74" textAnchor="middle">new reference</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="83" x2="480" y2="99" markerEnd="url(#ngc-p3)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-green"><rect x="350" y="100" width="260" height="46" rx="9" /><text x="480.0" y="120" textAnchor="middle" style={{fontSize:"13px"}}>OnPush: new input</text><text className="ngc-sub" x="480.0" y="138" textAnchor="middle">child is checked</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="147" x2="480" y2="163" markerEnd="url(#ngc-p3)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-green"><rect x="350" y="164" width="260" height="46" rx="9" /><text x="480.0" y="192" textAnchor="middle" style={{fontSize:"13px"}}>Child shows new name</text></g></g>
</svg>
<figcaption>OnPush checks an input only when it gets a new object.</figcaption>
</figure>

1. An `@Input()` gets a **new value**. For objects and arrays, "new" means a **new object**, not a changed property of the same object.
2. An event happens in the component's template, such as `(click)`.
3. A signal or an `async` pipe that its template reads gets a new value.

Rule 1 is the most important one. If the parent changes a property of the same object, an `OnPush` child does not see it:

```angular runnable title="OnPush needs a new object"
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

export interface Customer {
  name: string;
}

@Component({
  selector: 'app-customer-card',
  template: `<p>Customer: {{ customer.name }}</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCard {
  @Input() customer!: Customer;
}

@Component({
  selector: 'app-root',
  imports: [CustomerCard],
  template: `
    <button (click)="mutate()">Change the name (same object)</button>
    <button (click)="replace()">Change the name (new object)</button>
    <p>Parent sees: {{ customer.name }}</p>
    <app-customer-card [customer]="customer" />
  `,
})
export class App {
  customer: Customer = { name: 'Asha' };

  mutate() {
    this.customer.name = 'Ravi';  // same object: the OnPush child is skipped
  }

  replace() {
    this.customer = { ...this.customer, name: 'Meena' };  // new object: the child updates
  }
}
```

Click the first button: the parent shows "Ravi", but the card still shows "Asha". Click the second button: both show "Meena".

`{ ...this.customer, name: 'Meena' }` copies all properties into a new object and changes `name`. This is the same "create a new object" rule that you learned for signals.

`OnPush` and signals work very well together. When a component reads its data from signals, Angular knows exactly when it changed, so `OnPush` never misses an update. New Angular code often uses `OnPush` for every component.

## Avoiding Unnecessary Change Detection

- **Use `OnPush`** for components that receive their data through inputs or signals.
- **Do not call heavy methods in templates.** A method in a template runs on **every** check of the component. Use a **computed signal** instead: it recalculates only when its signals change.
- **Use signals** for state, so Angular updates only what changed.
- **Use `track`** in lists, so Angular reuses the existing elements (next section).
- **Split large components** into smaller ones, so `OnPush` can skip the parts that did not change.

```angular runnable title="A method in the template vs a computed signal"
import { Component, computed, signal } from '@angular/core';

export const calls = { method: 0, computed: 0 };

@Component({
  selector: 'app-root',
  template: `
    <p>Total (method): {{ totalMethod() }}</p>
    <p>Total (computed): {{ totalComputed() }}</p>
    <button (click)="clicks = clicks + 1">Unrelated click ({{ clicks }})</button>
    <button (click)="prices.update(list => [...list, 10])">Add a $10 item</button>
    <button (click)="report = 'Method ran ' + calls.method + ' times, computed ran ' + calls.computed + ' times.'">
      Show counts
    </button>
    <p>{{ report }}</p>
  `,
})
export class App {
  prices = signal([20, 35, 15]);
  clicks = 0;
  report = '';
  calls = calls;

  totalMethod() {
    calls.method++;
    return this.prices().reduce((sum, p) => sum + p, 0);
  }

  totalComputed = computed(() => {
    calls.computed++;
    return this.prices().reduce((sum, p) => sum + p, 0);
  });
}
```

Click **Unrelated click** a few times, then **Show counts**. The method runs on every check. The computed signal ran only once, because `prices` did not change. It runs again only after **Add a $10 item**.

## `track` Expression with `@for`

When a list changes, Angular must decide which elements to keep, move, create or remove. The **`track`** expression tells Angular how to recognize each item. You have used `track` since the Directives lesson.

<figure className="ngc-fig">
<svg viewBox="0 0 640 234" role="img" aria-label="After a reload, track by id keeps the three rows; track by object recreates all three.">
<defs><marker id="ngc-p4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><text className="ngc-title c-green" x="160" y="22" textAnchor="middle">track order.id</text><text className="ngc-title c-red" x="480" y="22" textAnchor="middle">track order</text><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-green"><rect x="30" y="36" width="260" height="46" rx="9" /><text x="160.0" y="56" textAnchor="middle" style={{fontSize:"13px"}}>Reload: new objects</text><text className="ngc-sub" x="160.0" y="74" textAnchor="middle">same ids 1, 2, 3</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="83" x2="160" y2="99" markerEnd="url(#ngc-p4)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-green"><rect x="30" y="100" width="260" height="46" rx="9" /><text x="160.0" y="120" textAnchor="middle" style={{fontSize:"13px"}}>Ids match</text><text className="ngc-sub" x="160.0" y="138" textAnchor="middle">rows are kept</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="147" x2="160" y2="163" markerEnd="url(#ngc-p4)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-green"><rect x="30" y="164" width="260" height="46" rx="9" /><text x="160.0" y="184" textAnchor="middle" style={{fontSize:"13px"}}>0 rows created</text><text className="ngc-sub" x="160.0" y="202" textAnchor="middle">values updated</text></g></g><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-red"><rect x="350" y="36" width="260" height="46" rx="9" /><text x="480.0" y="56" textAnchor="middle" style={{fontSize:"13px"}}>Reload: new objects</text><text className="ngc-sub" x="480.0" y="74" textAnchor="middle">same ids 1, 2, 3</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="83" x2="480" y2="99" markerEnd="url(#ngc-p4)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-red"><rect x="350" y="100" width="260" height="46" rx="9" /><text x="480.0" y="120" textAnchor="middle" style={{fontSize:"13px"}}>Objects differ</text><text className="ngc-sub" x="480.0" y="138" textAnchor="middle">old rows removed</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="147" x2="480" y2="163" markerEnd="url(#ngc-p4)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-red"><rect x="350" y="164" width="260" height="46" rx="9" /><text x="480.0" y="184" textAnchor="middle" style={{fontSize:"13px"}}>3 rows created</text><text className="ngc-sub" x="480.0" y="202" textAnchor="middle">more work</text></g></g>
</svg>
<figcaption>Tracking by a unique id lets Angular reuse the existing rows.</figcaption>
</figure>

- **`track item.id`**: Angular recognizes an item by its ID. When the list is loaded again with new objects but the same IDs, Angular **keeps** the existing elements and only updates their values.
- **`track $index`**: Angular recognizes items by their position. Use it only for simple lists that never change their order.
- **`track item`**: Angular recognizes the object itself. A new object with the same data counts as a **new** item, so its element is destroyed and created again.

The next example counts how many row components Angular creates. **Reload** creates new objects with the same IDs, as a server answer would.

```angular runnable title="track by ID reuses the rows"
import { Component, Input } from '@angular/core';

export const created = { byId: 0, byObject: 0 };

@Component({
  selector: 'app-row',
  template: `<span>{{ name }} </span>`,
})
export class Row {
  @Input() name = '';
  @Input() group = 'byId';

  ngOnInit() {
    (created as any)[this.group]++;
  }
}

function loadOrders() {
  return [
    { id: 1, name: 'Order 1001' },
    { id: 2, name: 'Order 1002' },
    { id: 3, name: 'Order 1003' },
  ];
}

@Component({
  selector: 'app-root',
  imports: [Row],
  template: `
    <button (click)="orders = loadOrders()">Reload the orders</button>
    <p>track order.id:
      @for (order of orders; track order.id) {
        <app-row [name]="order.name" group="byId" />
      }
    </p>
    <p>track order:
      @for (order of orders; track order) {
        <app-row [name]="order.name" group="byObject" />
      }
    </p>
    <button (click)="report = 'Rows created: track order.id = ' + created.byId + ', track order = ' + created.byObject">
      Show counts
    </button>
    <p>{{ report }}</p>
  `,
})
export class App {
  orders = loadOrders();
  loadOrders = loadOrders;
  report = '';
  created = created;
}
```

Click **Reload the orders** twice, then **Show counts**. With `track order.id`, the 3 rows were created once. With `track order`, Angular created 3 new rows on every reload.

## `trackBy` with `*ngFor`

Older Angular code uses the **`*ngFor`** directive instead of `@for`. You will see it in many existing projects. It works the same way, but the tracking is optional and is written as a **`trackBy`** function:

```html
<!-- Old syntax -->
<li *ngFor="let order of orders; trackBy: trackById">{{ order.name }}</li>

<!-- New syntax -->
@for (order of orders; track order.id) {
  <li>{{ order.name }}</li>
}
```

- Import `NgFor` from `@angular/common` and add it to `imports`.
- The `trackBy` function gets the index and the item, and returns the value that identifies the item.
- Without `trackBy`, `*ngFor` tracks the object itself, like `track order`. This is a common cause of slow lists.

```angular runnable title="*ngFor with trackBy"
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';

interface Order {
  id: number;
  name: string;
}

@Component({
  selector: 'app-root',
  imports: [NgFor],
  template: `
    <button (click)="reload()">Reload</button>
    <p>Updated {{ reloads }} times</p>
    <ul>
      <li *ngFor="let order of orders; trackBy: trackById">{{ order.name }}</li>
    </ul>
  `,
})
export class App {
  orders: Order[] = this.load();
  reloads = 0;

  load(): Order[] {
    return [
      { id: 1, name: 'Order 1001' },
      { id: 2, name: 'Order 1002' },
    ];
  }

  reload() {
    this.orders = this.load();
    this.reloads++;
  }

  trackById(index: number, order: Order) {
    return order.id;
  }
}
```

Prefer `@for` in new code: `track` is required there, so you cannot forget it.

## Optimizing Component Rendering

- **Render less.** Show long lists in pages (for example 50 rows per page) instead of all rows at once.
- **Keep templates simple.** Calculate values in computed signals, not in template expressions.
- **Use pure pipes** to format values. You learned pipes in the Pipes lesson. Angular runs a pure pipe again only when its input changes, like a computed signal.
- **Use `@if` for hidden parts.** Content inside a false `@if` is not created at all. Content hidden with CSS is still created and checked.
- **Avoid deep component trees** for simple layouts. Each component adds work during checks.

Do not optimize everything "just in case". Measure first. Most components are fast enough with the default settings, and extra optimizations make the code harder to read.

## Performance Best Practices

| Area | Best practice |
|---|---|
| Measurement | Measure with Lighthouse, the Performance tab and the Angular DevTools Profiler before and after a change |
| Loading | Lazy-load large and rarely used pages, and keep budgets in `angular.json` |
| Bundle | Build for production, import only what you use, check library sizes, optimize images |
| Change detection | Use signals and `OnPush`, and create new objects instead of changing old ones |
| Templates | No heavy methods in templates; use `computed()` and pure pipes |
| Lists | Always `track` by a unique ID; show long lists in pages |

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| Changing a property of an input object in an `OnPush` app | The child shows old data | Create a new object: `{ ...obj, name: 'x' }` |
| A heavy method in the template | The page becomes slow after every click | Use a computed signal |
| `track item` or `*ngFor` without `trackBy` for server data | Every reload creates all rows again | Track by a unique ID |
| All pages in the first download | The first page loads slowly | Lazy-load large pages |
| Optimizing without measuring | More complex code, but no faster app | Measure first, then optimize the slow part |

</div>

## Check Yourself

## Exercises

## What to Remember

- Measure first. Improve load performance with lazy loading and small production bundles, and runtime performance by doing less change detection work.
- `Default` components are checked on every run; `OnPush` components only when an input gets a new value, an event happens in them, or a signal they read changes. Always create new objects instead of changing old ones.
- Avoid heavy methods in templates (use `computed()`), and track list items by a unique ID with `track` in `@for` or `trackBy` in `*ngFor`.

Next: **Capstone - Employee Management System**, a complete app that combines everything you learned.
