Sending HTTP Requests (e.g. Connecting to a Database)Entry 4 of 7

Moving HTTP Code Out of Components

Putting request logic in a separate module so components only call simple, typed functions.

Notes

  • Components shouldn't know URLs, headers, or response shapes. A small http.ts module keeps that in one place.
  • In React this is an optional refactor. In Angular it's the default: HTTP code goes in a service from the start (Entry 2).
  • Each function returns the data the component needs (e.g. Place[], not the raw response) and throws on failure.
  • The same functions are reusable across components and easy to test.

What / Why / When

What

An API module of async functions.

Why

Cleaner components, one place to change URLs or add auth headers.

When

As soon as more than one component talks to the backend.

Use case

fetchAvailablePlaces() and updateUserPlaces() shared across the app.

Code compare

React
// http.tsexport type Place = { id: string; title: string };​const BASE_URL = "http://localhost:3000";​export async function fetchAvailablePlaces(): Promise<Place[]> {  const response = await fetch(`${BASE_URL}/places`);​  if (!response.ok) {    throw new Error("Failed to fetch places.");  }​  const resData: { places: Place[] } = await response.json();  return resData.places;}​export async function updateUserPlaces(places: Place[]): Promise<string> {  const response = await fetch(`${BASE_URL}/user-places`, {    method: "PUT",    body: JSON.stringify(places),    headers: { "Content-Type": "application/json" },  });​  if (!response.ok) {    throw new Error("Failed to update user data.");  }​  const resData: { message: string } = await response.json();  return resData.message;}
React
// Inside the component's effectconst places = await fetchAvailablePlaces();setAvailablePlaces(places);

This isn't an extra step in Angular: the service is where HTTP code lives from the beginning. Here is the complete PlacesService used in this chapter:

Angular
// places.service.tsimport { Injectable, inject } from "@angular/core";import { HttpClient, httpResource } from "@angular/common/http";import { map } from "rxjs";​export type Place = { id: string; title: string };​const BASE_URL = "http://localhost:3000";​@Injectable({ providedIn: "root" })export class PlacesService {  private http = inject(HttpClient);​  loadAvailablePlaces() {    return httpResource<{ places: Place[] }>(() => `${BASE_URL}/places`);  }​  fetchAvailablePlaces() {    return this.http      .get<{ places: Place[] }>(`${BASE_URL}/places`)      .pipe(map((resData) => resData.places));  }​  updateUserPlaces(places: Place[]) {    return this.http      .put<{ message: string }>(`${BASE_URL}/user-places`, places)      .pipe(map((resData) => resData.message));  }}

Differences and gotchas

  • HttpClient serializes the body to JSON and sets the Content-Type header automatically. With fetch, you do both by hand.
  • Cross-cutting concerns (auth tokens, logging, error toasts) go in HTTP interceptors in Angular. In React, add them to the API module or use a wrapper around fetch.

Quick reference

ReactAngular
Optional http.ts module of async functionsInjectable service (the convention)
JSON.stringify + Content-Type headerAutomatic
Wrapper around fetchHTTP interceptors