# Error Handling

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

> Finding and handling errors: runtime, dependency injection and routing errors, ErrorHandler and a custom error handler, fallback UI, error logging, debugging and Angular DevTools.

## Introduction to Angular Errors

An **error** is a problem that stops some code from finishing its work. In an Angular app, errors come from three places:

- **Your code:** for example, reading a property of `undefined`.
- **Angular:** for example, a missing provider or a route that does not exist.
- **The outside world:** for example, a server that does not answer. You handled these HTTP errors in the API Integration lesson.

Good error handling has two goals: the **user** sees a clear message instead of a broken page, and the **developer** gets enough information to fix the problem.

### Real-World Example

On a checkout page, a product from the server has no price. The template reads `product.price.amount`, and the page stops rendering. Without error handling, the customer sees half a page.

With error handling, a global error handler records the error and the page shows "Something went wrong. Please reload the page." The team sees the error in their log and fixes the data.

<figure className="ngc-fig">
<svg viewBox="0 0 640 152" role="img" aria-label="An uncaught error goes to the ErrorHandler, which writes it to a log for developers and shows a fallback UI for users.">
<defs><marker id="ngc-q1" 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-soft c-red"><rect x="6" y="36" width="142" height="64" rx="9" /><text x="77.0" y="65" textAnchor="middle" style={{fontSize:"14px"}}>Error</text><text className="ngc-sub" x="77.0" y="83" textAnchor="middle">in a click or template</text></g></g><line className="ngc-link ngc-flow c-slate" x1="150" y1="68" x2="162" y2="68" markerEnd="url(#ngc-q1)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-solid c-purple"><rect x="164" y="36" width="142" height="64" rx="9" /><text x="235.0" y="65" textAnchor="middle" style={{fontSize:"14px"}}>ErrorHandler</text><text className="ngc-sub" x="235.0" y="83" textAnchor="middle">handleError()</text></g></g><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="322" y="36" width="142" height="64" rx="9" /><text x="393.0" y="65" textAnchor="middle" style={{fontSize:"14px"}}>Log</text><text className="ngc-sub" x="393.0" y="83" textAnchor="middle">for developers</text></g></g><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="480" y="36" width="142" height="64" rx="9" /><text x="551.0" y="65" textAnchor="middle" style={{fontSize:"14px"}}>Fallback UI</text><text className="ngc-sub" x="551.0" y="83" textAnchor="middle">for users</text></g></g><line className="ngc-link ngc-flow c-slate" x1="306" y1="60" x2="320" y2="60" markerEnd="url(#ngc-q1)" /><path className="ngc-link ngc-flow c-slate" d="M236 102 C236 128 555 128 555 102" fill="none" markerEnd="url(#ngc-q1)" /><text className="ngc-note" x="320" y="140" textAnchor="middle">One handler receives every uncaught error, logs it and shows a clear message.</text>
</svg>
<figcaption>Global error handling in one place.</figcaption>
</figure>

Angular errors have a code, such as `NG0201`. Search for the code at `angular.dev/errors` to read what it means and how to fix it.

## Runtime Errors

A **runtime error** happens while the app is running, not when it is built. The most common one is reading a property of `null` or `undefined`:

<figure className="ngc-fig">
<svg viewBox="0 0 640 234" role="img" aria-label="user is null; user.name throws, but user?.name returns undefined and ?? gives Guest.">
<defs><marker id="ngc-e2" 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">user.name</text><text className="ngc-title c-green" x="480" y="22" textAnchor="middle">user?.name ?? 'Guest'</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="64" textAnchor="middle" style={{fontSize:"13px"}}>user = null</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="83" x2="160" y2="99" markerEnd="url(#ngc-e2)" /><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"}}>read .name</text><text className="ngc-sub" x="160.0" y="138" textAnchor="middle">TypeError</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="147" x2="160" y2="163" markerEnd="url(#ngc-e2)" /><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="184" textAnchor="middle" style={{fontSize:"13px"}}>Rendering stops</text><text className="ngc-sub" x="160.0" y="202" textAnchor="middle">half a page</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="64" textAnchor="middle" style={{fontSize:"13px"}}>user = null</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="83" x2="480" y2="99" markerEnd="url(#ngc-e2)" /><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"}}>?. stops safely</text><text className="ngc-sub" x="480.0" y="138" textAnchor="middle">undefined</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="147" x2="480" y2="163" markerEnd="url(#ngc-e2)" /><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="184" textAnchor="middle" style={{fontSize:"13px"}}>?? gives a default</text><text className="ngc-sub" x="480.0" y="202" textAnchor="middle">Hello, Guest</text></g></g>
</svg>
<figcaption>Optional chaining and ?? turn a crash into a default value.</figcaption>
</figure>

```text
TypeError: Cannot read properties of undefined (reading 'name')
```

When this happens in a template, Angular stops rendering that part of the page.

Ways to prevent it:

- **Optional chaining `?.`** stops and returns `undefined` when the value before it is `null` or `undefined`: `user?.name`.
- **`??`** gives a default value: `user?.name ?? 'Guest'`.
- **`@if`** renders the part only when the value exists.
- **`try` / `catch`** in TypeScript catches an error that a function throws, so the rest of the code can continue.

```angular runnable title="Preventing runtime errors"
import { Component } from '@angular/core';

interface User {
  name: string;
  address?: { city: string };
}

@Component({
  selector: 'app-root',
  template: `
    <p>Name: {{ user?.name ?? 'Guest' }}</p>
    <p>City: {{ user?.address?.city ?? 'not set' }}</p>
    @if (user) {
      <p>Welcome back, {{ user.name }}!</p>
    }
    <p>Saved settings: {{ settingsText }}</p>
  `,
})
export class App {
  user: User | null = { name: 'Asha' };
  settingsText = this.readSettings('{ broken json');

  readSettings(text: string) {
    try {
      return JSON.parse(text).theme;
    } catch {
      return 'could not read the settings, using the defaults';
    }
  }
}
```

`user.address` does not exist, but `?.` prevents the error. `JSON.parse()` throws an error for broken text, and `catch` returns a default message instead.

## Dependency Injection Errors

A **dependency injection error** happens when a class asks for something that Angular cannot provide. The most common one:

```text
NG0201: No provider for AnalyticsService found in NodeInjector
NullInjectorError: No provider for AnalyticsService!
```

The next block **fails on purpose**. `AnalyticsService` has `@Injectable()` without `providedIn`, and no `providers` array lists it.

```angular runnable title="A missing provider" expect-error
import { Component, Injectable, inject } from '@angular/core';

@Injectable()
export class AnalyticsService {}

@Component({
  selector: 'app-root',
  template: `<p>Dashboard</p>`,
})
export class App {
  analytics = inject(AnalyticsService);
}
```

How to fix it:

- Add `providedIn: 'root'` to `@Injectable()`, or list the service in a `providers` array.
- Check the import. Two classes with the same name in different files are two different tokens.
- If the dependency is optional, use `inject(AnalyticsService, { optional: true })`. It returns `null` instead of an error.

A block that fails shows the real Angular error in its output. Read the first line: it names the error code and the missing provider.

Another DI error is **`NG0203`**: `inject()` called outside an injection context, for example inside a click method. Call `inject()` only in a field or the constructor.

## Routing Errors

The router can fail in these ways:

| Problem | What happens | Fix |
|---|---|---|
| The URL matches no route | Error `NG04002: Cannot match any routes`, and the page stays the same | Add a wildcard route `'**'` with a "Page not found" component |
| `routerLink` has a wrong path | Same as above | Check the path, and use the wildcard route |
| A guard throws an error | The navigation is cancelled | Return `true` or a `UrlTree` from guards, never throw |
| A lazy-loaded file cannot download | The navigation fails, for example on a bad connection | Show a message and let the user try again |

`router.navigate()` returns a promise. When the navigation fails, the promise is **rejected**, so you can handle it with `.catch()`:

```angular runnable title="Handling a failed navigation"
import { Component, inject, signal } from '@angular/core';
import { Router, RouterOutlet, Routes } from '@angular/router';

@Component({ selector: 'app-home', template: `<p>🏠 Home page</p>` })
export class Home {}

export const routes: Routes = [{ path: '', component: Home }];

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  template: `
    <button (click)="open('/reports')">Open reports</button>
    @if (message()) {
      <p>⚠ {{ message() }}</p>
    }
    <router-outlet />
  `,
})
export class App {
  private router = inject(Router);
  message = signal('');

  open(url: string) {
    this.router.navigateByUrl(url).catch(() => {
      this.message.set(`The page ${url} does not exist.`);
    });
  }
}
```

There is no `reports` route, so the navigation fails and the message appears. In a real app, the wildcard route usually handles unknown URLs.

## Using `try`, `catch`, and `finally`

`try`, `catch` and `finally` are JavaScript statements for handling an error **inside one function**:

- **`try { ... }`** contains the code that can fail.
- **`catch (error) { ... }`** runs only when the code in `try` throws an error. The `error` variable contains the error.
- **`finally { ... }`** runs **always**, after `try` or after `catch`. Use it for work that must happen in both cases, such as hiding a loading message.

<figure className="ngc-fig">
<svg viewBox="0 0 640 234" role="img" aria-label="The try block runs; if it throws, catch runs; finally runs in both cases.">
<defs><marker id="ngc-e4" 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">No error</text><text className="ngc-title c-red" x="480" y="22" textAnchor="middle">Error in try</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"}}>try</text><text className="ngc-sub" x="160.0" y="74" textAnchor="middle">all code runs</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="83" x2="160" y2="99" markerEnd="url(#ngc-e4)" /><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"}}>catch</text><text className="ngc-sub" x="160.0" y="138" textAnchor="middle">skipped</text></g></g><line className="ngc-link ngc-flow c-slate" x1="160" y1="147" x2="160" y2="163" markerEnd="url(#ngc-e4)" /><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"}}>finally</text><text className="ngc-sub" x="160.0" y="202" textAnchor="middle">runs</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"}}>try</text><text className="ngc-sub" x="480.0" y="74" textAnchor="middle">stops at the error</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="83" x2="480" y2="99" markerEnd="url(#ngc-e4)" /><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"}}>catch (error)</text><text className="ngc-sub" x="480.0" y="138" textAnchor="middle">handles it</text></g></g><line className="ngc-link ngc-flow c-slate" x1="480" y1="147" x2="480" y2="163" markerEnd="url(#ngc-e4)" /><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"}}>finally</text><text className="ngc-sub" x="480.0" y="202" textAnchor="middle">runs</text></g></g>
</svg>
<figcaption>finally always runs, with or without an error.</figcaption>
</figure>

```ts
try {
  // code that can fail
} catch (error) {
  // runs only if try throws
} finally {
  // runs in every case
}
```

- When `try` throws, the rest of the `try` block is **skipped**, and the code continues in `catch`.
- After `catch`, the function continues normally. The error does not reach the global error handler.
- If you do not need the error, write `catch { ... }` without `(error)`.
- `catch` and `finally` are both optional, but you need at least one of them.

The next example reads saved settings. Click **Load valid settings** and **Load broken settings**: the broken text makes `JSON.parse()` throw, `catch` shows a message, and `finally` hides "Loading..." in both cases.

```angular runnable title="try, catch and finally"
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="load('{ &quot;theme&quot;: &quot;dark&quot; }')">Load valid settings</button>
    <button (click)="load('{ broken')">Load broken settings</button>
    @if (loading()) {
      <p>Loading...</p>
    }
    <p>Theme: {{ theme() }}</p>
    @if (error()) {
      <p>⚠ {{ error() }}</p>
    }
    <p>Steps: {{ steps().join(' → ') }}</p>
  `,
})
export class App {
  loading = signal(false);
  theme = signal('light');
  error = signal('');
  steps = signal<string[]>([]);

  load(text: string) {
    this.loading.set(true);
    this.error.set('');
    this.steps.set(['try']);
    try {
      const settings = JSON.parse(text); // throws for broken text
      this.theme.set(settings.theme);
      this.steps.update(list => [...list, 'parsed']);
    } catch (error) {
      this.error.set('The saved settings are broken. The default theme is used.');
      this.theme.set('light');
      this.steps.update(list => [...list, 'catch']);
    } finally {
      this.loading.set(false);
      this.steps.update(list => [...list, 'finally']);
    }
  }
}
```

After the valid settings, the steps are `try → parsed → finally`. After the broken settings, they are `try → catch → finally`: the `parsed` step is skipped.

`try` / `catch` catches only errors that happen **while** the `try` block runs. An error inside a `subscribe()` callback or a `setTimeout()` happens later, so it is not caught. For HTTP requests, use `catchError()` and `finalize()` from RxJS, which work the same way for Observables.

## Global Error Handling

You cannot put `try` / `catch` around every line. **Global error handling** catches every error that your code does not catch, in one place.

Angular sends these errors to one service:

- Errors in event handlers, such as `(click)`
- Errors while rendering templates
- Errors in lifecycle hooks and in `subscribe()` callbacks without an `error` function

## `ErrorHandler`

**`ErrorHandler`** is the Angular service that receives all uncaught errors. Its method `handleError(error)` is called for each one. The default `ErrorHandler` writes the error to the browser console with `console.error()`.

Open the browser console (F12, **Console** tab) and click the button below. The error appears in red, but the user sees nothing.

```angular runnable title="The default ErrorHandler writes to the console"
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="placeOrder()">Place order</button>
    <p>The page does not show the error. Look in the browser console.</p>
  `,
})
export class App {
  placeOrder() {
    throw new Error('Payment service is not configured');
  }
}
```

## Custom Error Handler

You can replace the default service with your own class. It implements `ErrorHandler` and is registered with a provider in `app.config.ts`:

```ts
export const appConfig: ApplicationConfig = {
  providers: [{ provide: ErrorHandler, useClass: AppErrorHandler }],
};
```

This is the `useClass` provider from the Dependency Injection lesson: when Angular asks for `ErrorHandler`, it gets an `AppErrorHandler`.

A custom handler usually does three things:

1. Writes the error to the console, so developers still see it.
2. Sends it to a **log** (the next sections).
3. Tells the app to show a **fallback UI** to the user.

```angular runnable title="A custom ErrorHandler with a fallback message"
import { ApplicationConfig, Component, ErrorHandler, Injectable, inject, signal } from '@angular/core';

// Shared state: the current error message for the user
@Injectable({ providedIn: 'root' })
export class ErrorState {
  readonly message = signal('');
}

@Injectable()
export class AppErrorHandler implements ErrorHandler {
  private state = inject(ErrorState);

  handleError(error: unknown) {
    console.error('AppErrorHandler:', error); // 1. for developers
    this.state.message.set('Something went wrong. Please try again.'); // 3. for users
  }
}

export const appConfig: ApplicationConfig = {
  providers: [{ provide: ErrorHandler, useClass: AppErrorHandler }],
};

@Component({
  selector: 'app-root',
  template: `
    @if (errors.message(); as message) {
      <p class="banner">⚠ {{ message }} <button (click)="errors.message.set('')">Close</button></p>
    }
    <button (click)="placeOrder()">Place order</button>
  `,
  styles: `.banner { background: #fee2e2; color: #991b1b; padding: 6px 10px; border-radius: 6px; }`,
})
export class App {
  errors = inject(ErrorState);

  placeOrder() {
    throw new Error('Payment service is not configured');
  }
}
```

Click **Place order**. The error goes to `AppErrorHandler`, which shows a banner instead of failing silently.

Never show the technical error text, such as a stack trace, to users. It confuses them and can reveal details about your code. Show a short, clear message, and keep the details in the log.

## Fallback UI

A **fallback UI** is what the user sees when something fails, instead of an empty or broken page. Common examples:

<figure className="ngc-fig">
<svg viewBox="0 0 640 234" role="img" aria-label="A product page where only the reviews section shows an error message with a Try again button.">
<rect x="120" y="8" width="400" height="190" rx="12" style={{fill:"none",stroke:"#9ca3af",strokeWidth:2}} /><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-blue"><rect x="140" y="24" width="360" height="44" rx="9" /><text x="320.0" y="51" textAnchor="middle" style={{fontSize:"14px"}}>Product: Wireless Headphones, $95</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-blue"><rect x="140" y="80" width="360" height="44" rx="9" /><text x="320.0" y="107" textAnchor="middle" style={{fontSize:"14px"}}>Description and photos</text></g></g><g className="ngc-step ngc-t3"><g className="ngc-node ngc-solid c-red ngc-pulse"><rect x="140" y="136" width="360" height="48" rx="9" /><text x="320.0" y="157" textAnchor="middle" style={{fontSize:"14px"}}>⚠ Reviews could not be loaded</text><text className="ngc-sub" x="320.0" y="175" textAnchor="middle">[ Try again ]</text></g></g><text className="ngc-note" x="320" y="222" textAnchor="middle">Only the failed part shows a message. The rest of the page keeps working.</text>
</svg>
<figcaption>A fallback inside one component keeps the page usable.</figcaption>
</figure>

- An **error banner** at the top of the page, as above
- An **error message inside one component**, while the rest of the page keeps working
- A **"Try again"** button that repeats the failed action
- A **"Page not found"** page for unknown routes

The next example shows a component-level fallback. Only the reviews section fails; the product information stays on the page.

```angular runnable title="A fallback inside one component"
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-reviews',
  template: `
    <h4>Reviews</h4>
    @if (failed()) {
      <p>Reviews could not be loaded. <button (click)="load()">Try again</button></p>
    } @else {
      @for (review of reviews(); track $index) {
        <p>⭐ {{ review }}</p>
      }
    }
  `,
})
export class Reviews {
  reviews = signal<string[]>([]);
  failed = signal(false);
  private attempts = 0;

  constructor() {
    this.load();
  }

  load() {
    this.attempts++;
    try {
      // The first attempt fails, the second one works
      if (this.attempts === 1) {
        throw new Error('Reviews server timeout');
      }
      this.reviews.set(['Great sound!', 'Comfortable to wear.']);
      this.failed.set(false);
    } catch {
      this.failed.set(true);
    }
  }
}

@Component({
  selector: 'app-root',
  imports: [Reviews],
  template: `
    <h3>Wireless Headphones</h3>
    <p>Price: $95</p>
    <app-reviews />
  `,
})
export class App {}
```

Click **Try again**: the second attempt works and the reviews appear.

## Error Logging

The console exists only in the user's browser. When a user has an error, the developers never see it. **Error logging** means collecting errors in a place where the team can read them.

Record useful details with each error:

- The error message
- The time
- The URL of the page
- What the user did, if known (for example, "clicked Place order")

The simplest way is a `LoggingService` that the error handler calls. In a real app, it sends each entry to a server with `HttpClient`, or to an error-monitoring service that your team uses.

```angular runnable title="An error log"
import { ApplicationConfig, Component, ErrorHandler, Injectable, inject, signal } from '@angular/core';

export interface LogEntry {
  time: string;
  message: string;
  page: string;
}

@Injectable({ providedIn: 'root' })
export class LoggingService {
  readonly entries = signal<LogEntry[]>([]);

  logError(error: unknown) {
    const entry: LogEntry = {
      time: new Date().toLocaleTimeString(),
      message: error instanceof Error ? error.message : String(error),
      page: location.pathname,
    };
    this.entries.update(list => [...list, entry]);
    // In a real app: this.http.post('/api/logs', entry).subscribe();
  }
}

@Injectable()
export class AppErrorHandler implements ErrorHandler {
  private logger = inject(LoggingService);
  handleError(error: unknown) {
    console.error(error);
    this.logger.logError(error);
  }
}

export const appConfig: ApplicationConfig = {
  providers: [{ provide: ErrorHandler, useClass: AppErrorHandler }],
};

@Component({
  selector: 'app-root',
  template: `
    <button (click)="save()">Save profile</button>
    <button (click)="upload()">Upload photo</button>
    <h4>Error log ({{ logger.entries().length }})</h4>
    @for (entry of logger.entries(); track $index) {
      <p>{{ entry.time }} | {{ entry.message }}</p>
    }
  `,
})
export class App {
  logger = inject(LoggingService);

  save() {
    throw new Error('Profile service returned no data');
  }

  upload() {
    throw new Error('Photo is larger than 5 MB');
  }
}
```

`error instanceof Error` checks if the value is a real `Error` object. Code can throw any value, so the check makes the message safe to read.

## Debugging Techniques

**Debugging** means finding the cause of an error. Useful techniques:

- **Read the full error message.** It names the problem and often the component and the line.
- **Look up the `NG` code** at `angular.dev/errors`.
- **`console.log()`** prints values, so you can check what the code really receives.
- **The `debugger;` statement** pauses the code at that line when the developer tools are open. Then you can read every variable and run the code step by step.
- **Breakpoints:** in the **Sources** tab of the developer tools, click a line number to pause there, without changing the code.
- **The Network tab** shows every HTTP request, its status code and its answer.
- **Development mode:** `ng serve` builds the app with extra checks and clearer error messages. Production builds remove them.

```ts
calculateTotal(items: CartItem[]) {
  console.log('items:', items);   // check the input
  debugger;                       // pause here when the developer tools are open
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
```

Remove `console.log()` and `debugger;` before you commit your code. They are for finding a problem, not for the finished app.

## Angular DevTools

**Angular DevTools** is a free browser extension for Chrome and Firefox, made by the Angular team. After you install it, the developer tools get an **Angular** tab when you open an Angular app in development mode.

| Feature | What it shows |
|---|---|
| Components | The tree of all components on the page |
| Properties | The inputs, outputs, signals and fields of the selected component. You can change them to test |
| Injector tree | Which injector provides each service |
| Router tree | All routes of the app |
| Profiler | How long each change detection run takes, and which components it checked |

In the **Console** tab, you can also select an element in the **Elements** tab and run `ng.getComponent($0)` to read the component that renders it. This works only in development mode.

## Error Handling Best Practices

- **Prevent errors first:** use `?.`, `??`, `@if` and TypeScript types.
- **Handle expected errors where they happen:** HTTP errors with `catchError()` or an `error` callback, and broken input with `try` / `catch`.
- **Add a custom `ErrorHandler`** for all other errors, so none of them is lost.
- **Show clear messages to users**, and keep technical details in the log.
- **Keep the rest of the page working:** show the fallback inside the failed component when you can.
- **Log errors to a server** with the time, the page and the message.
- **Add a wildcard route** for unknown URLs.
- **Test the error paths**, for example with a server that is turned off.

| Error type | Where to handle it |
|---|---|
| A value can be missing | Template: `?.`, `??`, `@if` |
| A function can throw | `try` / `catch` in that function |
| An HTTP request fails | `catchError()` or the `error` callback |
| An unknown URL | The wildcard route `'**'` |
| Everything else | A custom `ErrorHandler` |

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| `user.name` when `user` can be `null` | The template stops rendering | Write `user?.name` or use `@if (user)` |
| A service without `providedIn` or a provider | `NG0201: No provider for ...` | Add `providedIn: 'root'` or list it in `providers` |
| An empty `catch {}` that hides errors | The problem happens again and nobody knows why | Log the error, and show a message |
| Showing `error.stack` to users | Users see confusing technical text | Show a short message, log the details |
| A custom `ErrorHandler` that does not log | Developers cannot see the errors any more | Call `console.error()` and the logging service |
| No wildcard route | Unknown URLs leave the old page on screen | Add `{ path: '**', component: NotFound }` |

</div>

## Check Yourself

## Exercises

## What to Remember

- Prevent runtime errors with `?.`, `??` and `@if`, and catch expected errors where they happen: `try` / `catch` for functions and `catchError()` for HTTP.
- DI errors (`NG0201`) mean a missing provider; routing errors need a wildcard route and `.catch()` on `navigate()`. Look up every `NG` code at angular.dev/errors.
- Replace `ErrorHandler` with your own class to catch all other errors: log them for developers and show a fallback UI for users. Debug with the browser developer tools and Angular DevTools.

Next: **Performance Optimization**, how to make your app load fast and update efficiently.
