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

Fetching Data with useEffect

Loading data when a component appears, by running fetch inside an effect.

react.devuseEffect

Notes

  • Fetching is a side effect, so it goes in useEffect. Calling fetch in the component body and then setting state would cause an infinite loop (see Side Effects, Entry 1).
  • The effect callback can't be async itself, because React expects it to return nothing or a cleanup function, not a promise. Define an async function inside the effect and call it.
  • An empty dependency array ([]) fetches once, after the first render.

What / Why / When

What

Fetch on mount, store the result in state.

Why

The component renders immediately, then fills in data when it arrives.

When

Data needed as soon as a component is shown.

Use case

Loading the list of available places from the backend.

Code compare

React
import { useEffect, useState } from "react";​type Place = { id: string; title: string };​export function AvailablePlaces() {  const [availablePlaces, setAvailablePlaces] = useState<Place[]>([]);​  useEffect(() => {    async function fetchPlaces() {      const response = await fetch("http://localhost:3000/places");      const resData: { places: Place[] } = await response.json();      setAvailablePlaces(resData.places);    }​    fetchPlaces();  }, []);​  return (    <ul>      {availablePlaces.map((place) => (        <li key={place.id}>{place.title}</li>      ))}    </ul>  );}

In Angular, the request lives in a service, not in the component. The component injects the service and only consumes the result.

HttpClient must be provided once in the app config:

Angular
// app.config.tsimport { ApplicationConfig } from "@angular/core";import { provideHttpClient, withFetch } from "@angular/common/http";​export const appConfig: ApplicationConfig = {  providers: [provideHttpClient(withFetch())],};
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);​  // Signal-based (v19.2+): returns a resource with value(), isLoading(), error().  // Call it from a component field initializer or constructor (injection context).  loadAvailablePlaces() {    return httpResource<{ places: Place[] }>(() => `${BASE_URL}/places`);  }​  // Observable-based (classic)  fetchAvailablePlaces() {    return this.http      .get<{ places: Place[] }>(`${BASE_URL}/places`)      .pipe(map((resData) => resData.places));  }}

The component, using the signal-based method:

Angular
// available-places.component.tsimport { Component, computed, inject } from "@angular/core";import { PlacesService } from "./places.service";​@Component({  selector: "app-available-places",  template: `    <ul>      @for (place of availablePlaces(); track place.id) {        <li>{{ place.title }}</li>      }    </ul>  `,})export class AvailablePlacesComponent {  private placesResource = inject(PlacesService).loadAvailablePlaces();​  availablePlaces = computed(() =>    this.placesResource.hasValue() ? this.placesResource.value().places : [],  );}

Or with the classic Observable method, subscribed in ngOnInit:

Angular
import { Component, DestroyRef, OnInit, inject, signal } from "@angular/core";import { takeUntilDestroyed } from "@angular/core/rxjs-interop";import { Place, PlacesService } from "./places.service";​@Component({ selector: "app-available-places", template: `...` })export class AvailablePlacesComponent implements OnInit {  private placesService = inject(PlacesService);  private destroyRef = inject(DestroyRef);  availablePlaces = signal<Place[]>([]);​  ngOnInit() {    this.placesService      .fetchAvailablePlaces()      .pipe(takeUntilDestroyed(this.destroyRef))      .subscribe((places) => this.availablePlaces.set(places));  }}

Differences and gotchas

  • Component vs service. React examples fetch inside the component because React has no built-in service layer. In Angular, dependency injection makes a service the natural home for HTTP calls, so components stay focused on display.
  • HttpClient returns an Observable, which does nothing until you subscribe. fetch returns a Promise that starts immediately.
  • Your notes suggested fetching inside Angular's effect(). Prefer httpResource(), ngOnInit, or a service; the Angular docs discourage using effect() for loading data.
  • In React, useEffect fetching works but is low-level. Libraries such as TanStack Query, and React 19's use() with Suspense, handle caching and loading states for you.

Quick reference

ReactAngular
Request code in the componentRequest code in a service
fetch inside useEffect(..., [])Service method (httpResource() or HttpClient)
Async function defined inside the effectResource signals or Observable subscribe()
Result stored with useStateResult exposed as signals