Sending HTTP Requests (e.g. Connecting to a Database)Entry 3 of 7
Loading and Error States
Tracking whether a request is in progress or has failed, so the UI can show a spinner or an error message.
Notes
- A request has three outcomes to render: loading, error, and data. Each needs its own state.
fetchonly rejects on network failures. A404or500still resolves, so checkresponse.okand throw your own error.- Check
response.okbefore parsing: error responses may not contain valid JSON. - In TypeScript, a caught error has type
unknown. Narrow it witherr instanceof Errorbefore reading.message. - Use
finallyto turn loading off whether the request succeeded or failed.
What / Why / When
- What
isFetchinganderrorstate alongside the data.- Why
Users need feedback; silent failures look like empty data.
- When
Every request whose result is shown in the UI.
- Use case
Show "Fetching places…" while loading and an error panel if the server fails.
Code compare
import { useEffect, useState } from "react";type Place = { id: string; title: string };export function AvailablePlaces() { const [availablePlaces, setAvailablePlaces] = useState<Place[]>([]); const [isFetching, setIsFetching] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { async function fetchPlaces() { try { const response = await fetch("http://localhost:3000/places"); if (!response.ok) { throw new Error("Failed to fetch places."); } const resData: { places: Place[] } = await response.json(); setAvailablePlaces(resData.places); } catch (err) { setError(err instanceof Error ? err.message : "Could not fetch places."); } finally { setIsFetching(false); } } fetchPlaces(); }, []); if (error) return <p className="error">An error occurred: {error}</p>; if (isFetching) return <p>Fetching place data...</p>; return ( <ul> {availablePlaces.map((place) => ( <li key={place.id}>{place.title}</li> ))} </ul> );}The resource returned by PlacesService.loadAvailablePlaces() (Entry 2) tracks loading and error state for you. HttpClient also treats non-2xx responses as errors automatically, so there's no response.ok check.
import { Component, computed, inject } from "@angular/core";import { PlacesService } from "./places.service";@Component({ selector: "app-available-places", template: ` @if (placesResource.isLoading()) { <p>Fetching place data...</p> } @else if (placesResource.error()) { <p class="error">An error occurred: failed to fetch places.</p> } @else { <ul> @for (place of availablePlaces(); track place.id) { <li>{{ place.title }}</li> } </ul> } `,})export class AvailablePlacesComponent { protected placesResource = inject(PlacesService).loadAvailablePlaces(); availablePlaces = computed(() => this.placesResource.hasValue() ? this.placesResource.value().places : [], );}Differences and gotchas
- React needs three
useStatecalls and atry/catch/finally.httpResource()providesisLoading(),error(), andvalue()out of the box. fetchresolves on HTTP errors;HttpClientrejects on them. This is the most common bug when moving between the two.- Start
isFetchingastruein React. Starting it asfalsebriefly renders an empty list before the effect runs.
Quick reference
| React | Angular |
|---|---|
useState for loading, error, data | httpResource() signals |
if (!response.ok) throw ... | Automatic for non-2xx responses |
try / catch / finally | error callback or resource.error() |