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.tsmodule 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()andupdateUserPlaces()shared across the app.
Code compare
// 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;}// 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:
// 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
HttpClientserializes the body to JSON and sets theContent-Typeheader automatically. Withfetch, 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
| React | Angular |
|---|---|
Optional http.ts module of async functions | Injectable service (the convention) |
JSON.stringify + Content-Type header | Automatic |
Wrapper around fetch | HTTP interceptors |