# API Integration

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: Working with APIs
Source: https://www.devtutorials.ai/learn/angular/apis/api-integration

> Talking to a server with HttpClient: setup, environment files, the API service pattern, GET, POST, PUT and DELETE, headers, typed responses, loading indicators, error handling, interceptors, and a User Management capstone project.

## What Is `HttpClient`?

Most apps get their data from a **server**: a computer on the internet that stores the data. The app and the server talk through an **API** (Application Programming Interface): a list of URLs that the server answers, such as `/api/users`.

**`HttpClient`** is the Angular service that sends **HTTP requests** to a server and receives its **responses**. It is in the package `@angular/common/http`.

### Real-World Example

An HR app shows a list of employees. When the page opens, a `UserApiService` uses `HttpClient` to request `GET /api/users`, and the server answers with the list in JSON format.

When an HR manager adds a new employee, the service sends `POST /api/users` with the new employee's data, and the list updates.

<figure className="ngc-fig">
<svg viewBox="0 0 640 140" role="img" aria-label="A component calls UserApiService, which uses HttpClient to send a request to the server API; the response travels back to the component.">
<defs><marker id="ngc-h1" 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-blue"><rect x="6" y="40" width="140" height="64" rx="9" /><text x="76.0" y="69" textAnchor="middle" style={{fontSize:"14px"}}>Component</text><text className="ngc-sub" x="76.0" y="87" textAnchor="middle">calls api.getUsers()</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-purple"><rect x="166" y="40" width="150" height="64" rx="9" /><text x="241.0" y="69" textAnchor="middle" style={{fontSize:"14px"}}>UserApiService</text><text className="ngc-sub" x="241.0" y="87" textAnchor="middle">http.get(url)</text></g></g><g className="ngc-step ngc-t3"><g className="ngc-node ngc-solid c-orange"><rect x="336" y="40" width="130" height="64" rx="9" /><text x="401.0" y="69" textAnchor="middle" style={{fontSize:"14px"}}>HttpClient</text><text className="ngc-sub" x="401.0" y="87" textAnchor="middle">sends request</text></g></g><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="486" y="40" width="148" height="64" rx="9" /><text x="560.0" y="69" textAnchor="middle" style={{fontSize:"14px"}}>Server API</text><text className="ngc-sub" x="560.0" y="87" textAnchor="middle">GET /api/users</text></g></g><line className="ngc-link ngc-flow c-slate" x1="148" y1="62" x2="164" y2="62" markerEnd="url(#ngc-h1)" /><line className="ngc-link ngc-flow c-slate" x1="318" y1="62" x2="334" y2="62" markerEnd="url(#ngc-h1)" /><line className="ngc-link ngc-flow c-slate" x1="468" y1="62" x2="484" y2="62" markerEnd="url(#ngc-h1)" /><line className="ngc-link ngc-flow c-slate" x1="484" y1="86" x2="468" y2="86" markerEnd="url(#ngc-h1)" /><line className="ngc-link ngc-flow c-slate" x1="334" y1="86" x2="318" y2="86" markerEnd="url(#ngc-h1)" /><line className="ngc-link ngc-flow c-slate" x1="164" y1="86" x2="148" y2="86" markerEnd="url(#ngc-h1)" /><text className="ngc-note" x="320" y="28" textAnchor="middle">request →</text><text className="ngc-note" x="320" y="128" textAnchor="middle">← response: [ &#123; id: 1, name: 'Asha Rao' &#125;, ... ]</text>
</svg>
<figcaption>A request goes to the server, and the response comes back as an Observable.</figcaption>
</figure>

## Why `HttpClient`?

The browser already has a function to send requests, `fetch()`. `HttpClient` adds features that Angular apps need:

- **JSON automatically:** it converts the server's JSON answer into JavaScript objects for you.
- **Observables:** each request returns an Observable, so you can use the RxJS operators that you know, such as `map()`, `catchError()` and `switchMap()`.
- **Interceptors:** one function can change every request, for example to add a login token.
- **Typed responses:** you tell TypeScript what the answer contains, so the editor can check your code.
- **Easy testing:** Angular has tools to test code that uses `HttpClient` without a real server.

| Point | `fetch()` | `HttpClient` |
|---|---|---|
| Returns | A Promise | An Observable |
| JSON conversion | You call `response.json()` | Automatic |
| Cancel a request | Extra code | `unsubscribe()` or `switchMap()` |
| Change every request in one place | Not built in | Interceptors |

## Setting Up `HttpClient`

`HttpClient` is a service, but Angular does not provide it until you turn it on. Add `provideHttpClient()` to the providers in `app.config.ts`:

```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient()],
};
```

Then inject it in any component or service:

```ts
private http = inject(HttpClient);
```

The practice blocks cannot reach a real server. Each block declares its own `appConfig` in `app.ts`, and adds a small **practice server** function that answers the requests inside your browser. It is at the end of each block, and you do not need to read it now. The section "HTTP Interceptors" explains how it works.

The next example requests the list of users. `http.get()` returns an Observable, and the `async` pipe from the RxJS lesson subscribes to it and displays the result.

```angular runnable title="Your first GET request"
import { ApplicationConfig, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { delay, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <h4>Employees</h4>
    @for (user of users$ | async; track user.id) {
      <p>{{ user.name }} ({{ user.email }})</p>
    } @empty {
      <p>Loading...</p>
    }
  `,
})
export class App {
  private http = inject(HttpClient);
  users$ = this.http.get<any[]>('/api/users');
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

The request is sent when the `async` pipe subscribes. After 300 milliseconds, the practice server answers, and the list replaces "Loading...".

## Environment Configuration

The server URL is different in each place the app runs. On your computer it can be `http://localhost:3000`, and in the live app `https://api.myshop.com`. Do not write the URL in every service. Store it in an **environment file**.

Create the files with the CLI:

```bash
ng generate environments
```

This creates two files in `src/environments`:

```ts
// environment.ts — used by "ng build" for the live app
export const environment = {
  apiUrl: 'https://api.myshop.com',
};
```

```ts
// environment.development.ts — used by "ng serve" on your computer
export const environment = {
  apiUrl: 'http://localhost:3000',
};
```

Import `environment` from `environment.ts` in your code. When you run `ng serve`, the CLI uses the development file instead, automatically.

```ts
import { environment } from '../environments/environment';

const url = `${environment.apiUrl}/api/users`;
```

Environment files are part of the app that every user downloads. Never put passwords or secret keys in them.

## API Service Pattern

Do not call `HttpClient` directly in components. Put all requests for one kind of data in one **API service**, for example `UserApiService` for users.

- **One place for URLs:** when a URL changes, you edit one file.
- **Short components:** components call `api.getUsers()` and only display the result.
- **Reuse:** many components use the same methods.

```angular runnable title="A UserApiService"
import { ApplicationConfig, Component, Injectable, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { delay, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

const environment = { apiUrl: '/api' }; // from environment.ts in a real project

@Injectable({ providedIn: 'root' })
export class UserApiService {
  private http = inject(HttpClient);
  private baseUrl = `${environment.apiUrl}/users`;

  getUsers() {
    return this.http.get<any[]>(this.baseUrl);
  }

  getUser(id: number) {
    return this.http.get<any>(`${this.baseUrl}/${id}`);
  }
}

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <p>All users: {{ (users$ | async)?.length }}</p>
    <p>User 2: {{ (user2$ | async)?.name }}</p>
  `,
})
export class App {
  private api = inject(UserApiService);
  users$ = this.api.getUsers();
  user2$ = this.api.getUser(2);
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

The component does not know any URL. It only calls methods of `UserApiService`.

## HTTP Methods

Every HTTP request has a **method**. The method tells the server what to do with the data at the URL.

<figure className="ngc-fig">
<svg viewBox="0 0 640 224" role="img" aria-label="Four HTTP methods: GET reads, POST creates, PUT replaces and DELETE removes data on the server.">
<defs><marker id="ngc-h2" 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="10" width="110" height="40" rx="9" /><text x="65.0" y="35" textAnchor="middle" style={{fontSize:"14px"}}>GET</text></g></g><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-blue"><rect x="140" y="10" width="230" height="40" rx="9" /><text x="255.0" y="35" textAnchor="middle" style={{fontSize:"14px"}}>/api/users</text></g></g><line className="ngc-link ngc-flow c-slate" x1="372" y1="30" x2="452" y2="30" markerEnd="url(#ngc-h2)" /><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-blue"><rect x="454" y="10" width="176" height="40" rx="9" /><text x="542.0" y="35" textAnchor="middle" style={{fontSize:"14px"}}>read data</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-solid c-green"><rect x="10" y="62" width="110" height="40" rx="9" /><text x="65.0" y="87" textAnchor="middle" style={{fontSize:"14px"}}>POST</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-green"><rect x="140" y="62" width="230" height="40" rx="9" /><text x="255.0" y="87" textAnchor="middle" style={{fontSize:"14px"}}>/api/users + body</text></g></g><line className="ngc-link ngc-flow c-slate" x1="372" y1="82" x2="452" y2="82" markerEnd="url(#ngc-h2)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-green"><rect x="454" y="62" width="176" height="40" rx="9" /><text x="542.0" y="87" textAnchor="middle" style={{fontSize:"14px"}}>create data</text></g></g><g className="ngc-step ngc-t3"><g className="ngc-node ngc-solid c-orange"><rect x="10" y="114" width="110" height="40" rx="9" /><text x="65.0" y="139" textAnchor="middle" style={{fontSize:"14px"}}>PUT</text></g></g><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="140" y="114" width="230" height="40" rx="9" /><text x="255.0" y="139" textAnchor="middle" style={{fontSize:"14px"}}>/api/users/1 + body</text></g></g><line className="ngc-link ngc-flow c-slate" x1="372" y1="134" x2="452" y2="134" markerEnd="url(#ngc-h2)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="454" y="114" width="176" height="40" rx="9" /><text x="542.0" y="139" textAnchor="middle" style={{fontSize:"14px"}}>replace data</text></g></g><g className="ngc-step ngc-t4"><g className="ngc-node ngc-solid c-red"><rect x="10" y="166" width="110" height="40" rx="9" /><text x="65.0" y="191" textAnchor="middle" style={{fontSize:"14px"}}>DELETE</text></g></g><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-red"><rect x="140" y="166" width="230" height="40" rx="9" /><text x="255.0" y="191" textAnchor="middle" style={{fontSize:"14px"}}>/api/users/1</text></g></g><line className="ngc-link ngc-flow c-slate" x1="372" y1="186" x2="452" y2="186" markerEnd="url(#ngc-h2)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-red"><rect x="454" y="166" width="176" height="40" rx="9" /><text x="542.0" y="191" textAnchor="middle" style={{fontSize:"14px"}}>remove data</text></g></g>
</svg>
<figcaption>The method tells the server what to do with the data at the URL.</figcaption>
</figure>

| Method | What it does | `HttpClient` call | Example |
|---|---|---|---|
| GET | Reads data | `http.get(url)` | Get all users |
| POST | Creates new data | `http.post(url, body)` | Add a user |
| PUT | Replaces existing data | `http.put(url, body)` | Save an edited user |
| DELETE | Removes data | `http.delete(url)` | Delete a user |

The **body** is the data you send to the server, for example the new user. GET and DELETE requests have no body.

### GET Requests

`get()` reads data. You saw it above:

```ts
getUsers() {
  return this.http.get<User[]>('/api/users');
}
```

### POST Requests

`post()` sends new data to the server. The server saves it and usually answers with the saved object, including its new `id`:

```ts
addUser(user: NewUser) {
  return this.http.post<User>('/api/users', user);
}
```

### PUT Requests

`put()` sends the full, changed object for an existing `id`. The server replaces the old data:

```ts
updateUser(user: User) {
  return this.http.put<User>(`/api/users/${user.id}`, user);
}
```

### DELETE Requests

`delete()` removes the data with this `id`:

```ts
deleteUser(id: number) {
  return this.http.delete<void>(`/api/users/${id}`);
}
```

An `HttpClient` method only **prepares** the request. The request is sent when something subscribes: the `async` pipe or `subscribe()`. If you call `api.addUser(user)` without `.subscribe()`, nothing is sent.

The next example uses all four methods. The list is stored in a `BehaviorSubject` from the RxJS lesson, and `load()` requests it again after every change.

```angular runnable title="GET, POST, PUT and DELETE"
import { ApplicationConfig, Component, Injectable, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { BehaviorSubject, delay, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

@Injectable({ providedIn: 'root' })
export class UserApiService {
  private http = inject(HttpClient);

  getUsers() {
    return this.http.get<any[]>('/api/users');
  }
  addUser(user: any) {
    return this.http.post<any>('/api/users', user);
  }
  updateUser(user: any) {
    return this.http.put<any>(`/api/users/${user.id}`, user);
  }
  deleteUser(id: number) {
    return this.http.delete<void>(`/api/users/${id}`);
  }
}

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <button (click)="add()">POST: add Meena</button>
    <button (click)="rename()">PUT: rename user 1</button>
    <button (click)="remove()">DELETE: user 2</button>
    @for (user of users$ | async; track user.id) {
      <p>#{{ user.id }} {{ user.name }}</p>
    }
  `,
})
export class App {
  private api = inject(UserApiService);
  users$ = new BehaviorSubject<any[]>([]);

  constructor() {
    this.load();
  }

  load() {
    this.api.getUsers().subscribe(list => this.users$.next(list));
  }

  add() {
    this.api.addUser({ name: 'Meena Iyer', email: 'meena@shop.com' }).subscribe(() => this.load());
  }

  rename() {
    this.api.updateUser({ id: 1, name: 'Asha R.', email: 'asha@shop.com' }).subscribe(() => this.load());
  }

  remove() {
    this.api.deleteUser(2).subscribe(() => this.load());
  }
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

Click the three buttons. Each one sends a request, and when the server answers, `load()` gets the new list.

## Request Headers

**Headers** are extra information sent with a request, as name and value pairs. Common headers:

- `Authorization`: proves who the user is, for example `Bearer abc123`.
- `Content-Type`: the format of the body. `HttpClient` sets `application/json` for you.
- `Accept-Language`: the language the user wants, for example `en`.

Pass headers in the options object, the last argument of each method:

```angular runnable title="Sending headers with a request"
import { ApplicationConfig, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpHeaders, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { delay, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <p>The server received these headers:</p>
    @for (line of received$ | async; track $index) {
      <p>{{ line }}</p>
    }
  `,
})
export class App {
  private http = inject(HttpClient);

  private headers = new HttpHeaders({
    'Accept-Language': 'en',
    'X-App-Version': '2.1',
  });

  // This practice URL answers with the headers it received.
  received$ = this.http.get<string[]>('/api/echo-headers', { headers: this.headers });
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

To add the same header to **every** request, such as `Authorization`, use an interceptor. You learn it below.

## Typed API Responses

`http.get()` does not know what the server sends. You tell TypeScript with an **interface** and a type in `< >`:

```ts
export interface User {
  id: number;
  name: string;
  email: string;
}

getUsers() {
  return this.http.get<User[]>('/api/users'); // an Observable of a list of User
}
```

- The editor now knows that `user.name` exists, and marks typing mistakes such as `user.nmae`.
- The type is only a **promise** to TypeScript. It does not check the server's real answer. If the server sends different fields, your code still receives them.
- For a new user without an `id`, use a second type: `type NewUser = Omit<User, 'id'>`. `Omit` creates a copy of a type without the fields that you list.

## Loading Indicators

A request takes time. During this time, show a **loading indicator**, for example the text "Loading..." or a spinner, so the user knows the app is working.

<figure className="ngc-fig">
<svg viewBox="0 0 640 134" role="img" aria-label="Loading is set to true, the request is sent, the response arrives, and finalize sets loading to false.">
<defs><marker id="ngc-h3" 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-orange"><rect x="6" y="30" width="145" height="64" rx="9" /><text x="78.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>loading = true</text><text className="ngc-sub" x="78.5" y="77" textAnchor="middle">show "Loading..."</text></g></g><line className="ngc-link ngc-flow c-slate" x1="152" y1="62" x2="166" y2="62" markerEnd="url(#ngc-h3)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-blue"><rect x="167" y="30" width="145" height="64" rx="9" /><text x="239.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>Request sent</text><text className="ngc-sub" x="239.5" y="77" textAnchor="middle">GET /api/users</text></g></g><line className="ngc-link ngc-flow c-slate" x1="313" y1="62" x2="327" y2="62" markerEnd="url(#ngc-h3)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-purple"><rect x="328" y="30" width="145" height="64" rx="9" /><text x="400.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>Answer or error</text><text className="ngc-sub" x="400.5" y="77" textAnchor="middle">after some time</text></g></g><line className="ngc-link ngc-flow c-slate" x1="474" y1="62" x2="488" y2="62" markerEnd="url(#ngc-h3)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="489" y="30" width="145" height="64" rx="9" /><text x="561.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>finalize()</text><text className="ngc-sub" x="561.5" y="77" textAnchor="middle">loading = false</text></g></g><circle className="ngc-packet c-orange" r="5"><animateMotion dur="3.6s" repeatCount="indefinite" path="M78 106 L562 106" /></circle><text className="ngc-note" x="320" y="128" textAnchor="middle">finalize() runs after success and after an error, so the indicator never stays.</text>
</svg>
<figcaption>A loading indicator covers the time between the request and the answer.</figcaption>
</figure>

1. Before the request, set `loading` to `true`.
2. When the request ends, set it to `false`. Use `finalize()` from the RxJS lesson, because it runs after success **and** after an error.

```angular runnable title="A loading indicator"
import { ApplicationConfig, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { BehaviorSubject, delay, finalize, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <button (click)="load()" [disabled]="loading$ | async">Reload users</button>
    @if (loading$ | async) {
      <p>⏳ Loading...</p>
    } @else {
      <p>{{ count$ | async }} users loaded.</p>
    }
  `,
})
export class App {
  private http = inject(HttpClient);
  loading$ = new BehaviorSubject(false);
  count$ = new BehaviorSubject(0);

  constructor() {
    this.load();
  }

  load() {
    this.loading$.next(true);
    this.http
      .get<any[]>('/api/users')
      .pipe(finalize(() => this.loading$.next(false)))
      .subscribe(list => this.count$.next(list.length));
  }
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

Click **Reload users**. The button is disabled and "Loading..." is shown until the server answers.

## HTTP Error Handling

A request can fail. When it fails, `HttpClient` sends an **error** through the Observable, with an `HttpErrorResponse` object. Its most useful properties:

<figure className="ngc-fig">
<svg viewBox="0 0 640 134" role="img" aria-label="A request fails with 404 or 500; catchError shows a message and returns a replacement value.">
<defs><marker id="ngc-h4" 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="6" y="30" width="145" height="64" rx="9" /><text x="78.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>Request</text><text className="ngc-sub" x="78.5" y="77" textAnchor="middle">GET /api/users/99</text></g></g><line className="ngc-link ngc-flow c-slate" x1="152" y1="62" x2="166" y2="62" markerEnd="url(#ngc-h4)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-red"><rect x="167" y="30" width="145" height="64" rx="9" /><text x="239.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>Server</text><text className="ngc-sub" x="239.5" y="77" textAnchor="middle">404 Not Found</text></g></g><line className="ngc-link ngc-flow c-slate" x1="313" y1="62" x2="327" y2="62" markerEnd="url(#ngc-h4)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="328" y="30" width="145" height="64" rx="9" /><text x="400.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>catchError()</text><text className="ngc-sub" x="400.5" y="77" textAnchor="middle">show a clear message</text></g></g><line className="ngc-link ngc-flow c-slate" x1="474" y1="62" x2="488" y2="62" markerEnd="url(#ngc-h4)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="489" y="30" width="145" height="64" rx="9" /><text x="561.5" y="59" textAnchor="middle" style={{fontSize:"14px"}}>of(null)</text><text className="ngc-sub" x="561.5" y="77" textAnchor="middle">page keeps working</text></g></g><circle className="ngc-packet c-red" r="5"><animateMotion dur="3.6s" repeatCount="indefinite" path="M78 106 L562 106" /></circle><text className="ngc-note" x="320" y="128" textAnchor="middle">Without catchError, the error stops the Observable and the page stays empty.</text>
</svg>
<figcaption>catchError() turns a failed request into a message and a safe value.</figcaption>
</figure>

- **`status`**: the HTTP status code, for example `404` or `500`. It is `0` when the server cannot be reached at all.
- **`statusText`**: a short text, for example `Not Found`.
- **`message`**: a full error message.

Common status codes:

| Code | Meaning | Message for the user |
|---|---|---|
| 400 | The request data is wrong | "Please check the form." |
| 401 | The user is not logged in | "Please log in again." |
| 404 | The data does not exist | "This user was not found." |
| 500 | The server has a problem | "Something went wrong. Please try again later." |
| 0 | No connection to the server | "Check your internet connection." |

Use `catchError()` to show a clear message and return a replacement value:

```angular runnable title="Handling a failed request"
import { ApplicationConfig, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { BehaviorSubject, catchError, delay, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <p>User 99: {{ (user$ | async)?.name ?? '-' }}</p>
    <p>Reports: {{ (reports$ | async)?.length ?? '-' }}</p>
    @for (message of errors$ | async; track $index) {
      <p>⚠ {{ message }}</p>
    }
  `,
})
export class App {
  private http = inject(HttpClient);
  errors$ = new BehaviorSubject<string[]>([]);

  user$ = this.http.get<any>('/api/users/99').pipe(
    catchError((error: HttpErrorResponse) => {
      this.showError(error);
      return of(null);
    }),
  );

  reports$ = this.http.get<any[]>('/api/broken').pipe(
    catchError((error: HttpErrorResponse) => {
      this.showError(error);
      return of([]);
    }),
  );

  showError(error: HttpErrorResponse) {
    const text = error.status === 404 ? 'This user was not found.' : 'Something went wrong. Please try again later.';
    this.errors$.next([...this.errors$.value, `${error.status} ${error.statusText}: ${text}`]);
  }
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

User 99 does not exist, so the server answers 404. The second URL fails with 500. Both errors are caught, and the page shows a message for each.

## HTTP Interceptors

An **interceptor** is a function that runs for **every** request that `HttpClient` sends. It can change the request before it leaves, and read or change the response.

<figure className="ngc-fig">
<svg viewBox="0 0 640 140" role="img" aria-label="A request passes through the auth and logging interceptors before the server; the response returns through them.">
<defs><marker id="ngc-h5" 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="6" y="24" width="145" height="64" rx="9" /><text x="78.5" y="53" textAnchor="middle" style={{fontSize:"14px"}}>HttpClient</text><text className="ngc-sub" x="78.5" y="71" textAnchor="middle">http.get(url)</text></g></g><line className="ngc-link ngc-flow c-slate" x1="152" y1="56" x2="166" y2="56" markerEnd="url(#ngc-h5)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-orange"><rect x="167" y="24" width="145" height="64" rx="9" /><text x="239.5" y="53" textAnchor="middle" style={{fontSize:"14px"}}>authInterceptor</text><text className="ngc-sub" x="239.5" y="71" textAnchor="middle">adds Authorization</text></g></g><line className="ngc-link ngc-flow c-slate" x1="313" y1="56" x2="327" y2="56" markerEnd="url(#ngc-h5)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-purple"><rect x="328" y="24" width="145" height="64" rx="9" /><text x="400.5" y="53" textAnchor="middle" style={{fontSize:"14px"}}>loggingInterceptor</text><text className="ngc-sub" x="400.5" y="71" textAnchor="middle">writes a log line</text></g></g><line className="ngc-link ngc-flow c-slate" x1="474" y1="56" x2="488" y2="56" markerEnd="url(#ngc-h5)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="489" y="24" width="145" height="64" rx="9" /><text x="561.5" y="53" textAnchor="middle" style={{fontSize:"14px"}}>Server</text><text className="ngc-sub" x="561.5" y="71" textAnchor="middle">answers</text></g></g><circle className="ngc-packet c-blue" r="5"><animateMotion dur="3.6s" repeatCount="indefinite" path="M78 100 L562 100" /></circle><circle className="ngc-packet c-green" r="5"><animateMotion dur="3.2s" repeatCount="indefinite" path="M560 112 L80 112" /></circle><text className="ngc-note" x="320" y="128" textAnchor="middle">← the response comes back through the same interceptors</text>
</svg>
<figcaption>Every request passes through each interceptor in order.</figcaption>
</figure>

Typical uses:

- Add an `Authorization` header with the login token to every request.
- Write a log line for every request.
- Show one error message for all failed requests.

An interceptor gets two arguments:

- **`req`**: the request. Requests cannot be changed, so you create a changed copy with `req.clone(...)`.
- **`next`**: a function that passes the request on. `next(req)` sends it to the next interceptor, or to the server.

```ts
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const copy = req.clone({ setHeaders: { Authorization: 'Bearer abc123' } });
  return next(copy);
};
```

Register interceptors with `withInterceptors()`. They run in the order of the list:

```ts
providers: [provideHttpClient(withInterceptors([authInterceptor, loggingInterceptor]))],
```

The **practice server** in these blocks is an interceptor too. It is the last one in the list, and it returns a response itself instead of calling `next(req)`. So the request never leaves the browser.

```angular runnable title="Two interceptors"
import { ApplicationConfig, Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { delay, mergeMap, of, tap, throwError, timer } from 'rxjs';

export const requestLog: string[] = [];

// 1. Adds the login token to every request
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const copy = req.clone({ setHeaders: { Authorization: 'Bearer abc123' } });
  return next(copy);
};

// 2. Writes one log line for every response
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    tap(event => {
      if (event instanceof HttpResponse) {
        requestLog.push(`${req.method} ${req.url} → ${event.status}`);
      }
    }),
  );
};

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor, loggingInterceptor, practiceServer])),
  ],
};

@Component({
  selector: 'app-root',
  imports: [AsyncPipe],
  template: `
    <p>Headers that reached the server:</p>
    @for (line of received$ | async; track $index) {
      <p>{{ line }}</p>
    }
    <p>Log: {{ log.join(', ') }}</p>
  `,
})
export class App {
  private http = inject(HttpClient);
  received$ = this.http.get<string[]>('/api/echo-headers');
  log = requestLog;
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

The component did not add the `Authorization` header, but the server received it, because `authInterceptor` added it.

## Capstone Project

### User Management System

Now you combine everything from this lesson in one small app: a **User Management System** for an HR team. It can:

- **Get users:** show the list when the page opens.
- **Add user:** fill in the form and click **Add**.
- **Edit user:** click **Edit**, change the form and click **Save**.
- **Delete user:** click **Delete** next to a user.

The app has three parts:

| Part | Job |
|---|---|
| `User` interface | Describes one user: `id`, `name` and `email` |
| `UserApiService` | All four HTTP requests |
| `App` component | The form, the list, the loading text and the error message |

The form uses `ngModel` from the Template-Driven Forms lesson.

### Get Users

`load()` calls `getUsers()` when the page opens and after every change, and puts the list into `users$`.

### Add User

When no user is being edited, **Add** calls `addUser()` with the form values. The server answers with the new user, including its new `id`.

### Edit User

**Edit** copies the user into the form and stores its `id` in `editingId`. Then the button shows **Save**, and it calls `updateUser()`.

### Delete User

**Delete** calls `deleteUser()` with the user's `id`, then reloads the list.

```angular runnable title="Capstone: User Management System"
import { ApplicationConfig, Component, Injectable, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
  HttpClient, HttpErrorResponse, HttpHandlerFn, HttpRequest, HttpResponse,
  provideHttpClient, withInterceptors,
} from '@angular/common/http';
import { BehaviorSubject, delay, finalize, mergeMap, of, throwError, timer } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([practiceServer]))],
};

const environment = { apiUrl: '/api' };

// ── Model ──
export interface User {
  id: number;
  name: string;
  email: string;
}

// ── API service ──
@Injectable({ providedIn: 'root' })
export class UserApiService {
  private http = inject(HttpClient);
  private url = `${environment.apiUrl}/users`;

  getUsers() {
    return this.http.get<User[]>(this.url);
  }
  addUser(name: string, email: string) {
    return this.http.post<User>(this.url, { name, email });
  }
  updateUser(user: User) {
    return this.http.put<User>(`${this.url}/${user.id}`, user);
  }
  deleteUser(id: number) {
    return this.http.delete<void>(`${this.url}/${id}`);
  }
}

// ── Component ──
@Component({
  selector: 'app-root',
  imports: [AsyncPipe, FormsModule],
  template: `
    <h4>User Management</h4>
    <form (ngSubmit)="save()">
      <input name="name" [(ngModel)]="name" placeholder="Name" required />
      <input name="email" [(ngModel)]="email" placeholder="Email" required />
      <button type="submit">{{ editingId ? 'Save' : 'Add' }}</button>
      @if (editingId) {
        <button type="button" (click)="cancel()">Cancel</button>
      }
    </form>

    @if (loading$ | async) {
      <p>⏳ Loading...</p>
    }
    @if (error$ | async; as error) {
      <p>⚠ {{ error }}</p>
    }

    @for (user of users$ | async; track user.id) {
      <p>
        {{ user.name }} ({{ user.email }})
        <button (click)="edit(user)">Edit</button>
        <button (click)="remove(user.id)">Delete</button>
      </p>
    } @empty {
      <p>No users.</p>
    }
  `,
})
export class App {
  private api = inject(UserApiService);

  users$ = new BehaviorSubject<User[]>([]);
  loading$ = new BehaviorSubject(false);
  error$ = new BehaviorSubject('');

  name = '';
  email = '';
  editingId: number | null = null;

  constructor() {
    this.load();
  }

  load() {
    this.loading$.next(true);
    this.api
      .getUsers()
      .pipe(finalize(() => this.loading$.next(false)))
      .subscribe({
        next: list => this.users$.next(list),
        error: () => this.error$.next('Could not load users.'),
      });
  }

  save() {
    if (!this.name || !this.email) {
      this.error$.next('Name and email are required.');
      return;
    }
    this.error$.next('');
    const request = this.editingId
      ? this.api.updateUser({ id: this.editingId, name: this.name, email: this.email })
      : this.api.addUser(this.name, this.email);
    request.subscribe({
      next: () => {
        this.cancel();
        this.load();
      },
      error: () => this.error$.next('Could not save the user.'),
    });
  }

  edit(user: User) {
    this.editingId = user.id;
    this.name = user.name;
    this.email = user.email;
  }

  cancel() {
    this.editingId = null;
    this.name = '';
    this.email = '';
  }

  remove(id: number) {
    this.api.deleteUser(id).subscribe({
      next: () => this.load(),
      error: () => this.error$.next('Could not delete the user.'),
    });
  }
}
// ─── Practice server ──────────────────────────────────────────────
// The practice blocks cannot reach a real server. This function answers
// the requests inside the browser. You learn how it works in the
// section "HTTP Interceptors". You do not need to read it now.
let users = [
  { id: 1, name: 'Asha Rao', email: 'asha@shop.com' },
  { id: 2, name: 'Ravi Kumar', email: 'ravi@shop.com' },
];
let nextId = 3;

function practiceServer(req: HttpRequest<any>, next: HttpHandlerFn) {
  const id = Number(req.url.split('/')[3]);
  if (req.url === '/api/broken') return fail(500, 'Internal Server Error');
  if (req.url === '/api/echo-headers') {
    return reply(req.headers.keys().map(key => `${key}: ${req.headers.get(key)}`));
  }
  if (req.url === '/api/users' && req.method === 'GET') return reply(users);
  if (req.url === '/api/users' && req.method === 'POST') {
    const user = { ...req.body, id: nextId++ };
    users = [...users, user];
    return reply(user, 201);
  }
  if (req.method === 'GET') {
    const user = users.find(u => u.id === id);
    return user ? reply(user) : fail(404, 'Not Found');
  }
  if (req.method === 'PUT') {
    users = users.map(u => (u.id === id ? { ...req.body, id } : u));
    return reply({ ...req.body, id });
  }
  if (req.method === 'DELETE') {
    users = users.filter(u => u.id !== id);
    return reply(null, 204);
  }
  return fail(404, 'Not Found');
}

function reply(body: unknown, status = 200) {
  return of(new HttpResponse({ status, body })).pipe(delay(300));
}

function fail(status: number, statusText: string) {
  return timer(300).pipe(mergeMap(() => throwError(() => new HttpErrorResponse({ status, statusText }))));
}

```

Try all four actions: add a user, edit one, delete one, and click **Add** with an empty form to see the error message.

## Download Project Files

### Download API Project Source Code

<a href="/downloads/angular-user-management.zip" download>Download angular-user-management.zip</a>

The ZIP file contains the capstone as a complete Angular CLI project, with a small **real** server written in Node.js. In this project, the requests leave the browser and go to the server, so there is no practice server.

| Folder or file | What it contains |
|---|---|
| `src/app/user.model.ts` | The `User` interface |
| `src/app/user-api.service.ts` | `UserApiService` with GET, POST, PUT and DELETE |
| `src/app/auth.interceptor.ts` | An interceptor that adds an `Authorization` header |
| `src/app/app.ts`, `app.html` | The User Management component |
| `src/environments/` | `apiUrl` for development and for the live app |
| `server/server.js` | The API server. It stores the users in memory |

### Project Setup Instructions

You need **Node.js 20.19 or newer**. Check your version with `node -v`.

1. Unzip the file, open a terminal and go to the project folder:

   ```bash
   cd angular-user-management
   ```

2. Install the packages:

   ```bash
   npm install
   ```

3. Start the API server. It runs at `http://localhost:3000`. Keep this terminal open:

   ```bash
   npm run api
   ```

4. Open a **second** terminal in the same folder and start the Angular app:

   ```bash
   npm start
   ```

5. Open `http://localhost:4200` in your browser.

The server keeps the users in memory, so the list returns to the first two users each time you restart `npm run api`.

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| No `provideHttpClient()` in `app.config.ts` | Error: No provider for HttpClient | Add `provideHttpClient()` to the providers |
| Calling `api.addUser(...)` without `subscribe()` | No request is sent | Call `.subscribe()`, or use the `async` pipe |
| Using the result outside `subscribe()` | The value is still empty, because the answer has not arrived | Use the value inside `subscribe()` or with the `async` pipe |
| No error handling | A failed request leaves the page empty, with no message | Add `catchError()` or an `error` callback |
| Writing the server URL in every service | Many files to change for the live app | Store `apiUrl` in the environment files |
| Changing `req` in an interceptor | Error, because requests cannot be changed | Use `req.clone(...)` |

</div>

## Check Yourself

## Exercises

## What to Remember

- `HttpClient` sends requests to a server. Turn it on with `provideHttpClient()`, keep all requests in an API service, and store the server URL in the environment files.
- `get()` reads, `post()` creates, `put()` replaces and `delete()` removes data. Each returns an Observable, and the request is sent only when something subscribes.
- Show a loading indicator with `finalize()`, handle errors with `catchError()`, and use interceptors for work that every request needs, such as an `Authorization` header.

Next: **Angular Signals**, a simple way to store values that update the screen.
