Kurs Angular · Moduł 9: HttpClient

Wzorce HTTP - trzy pokolenia

3 min czytania ·
W tej lekcji3

Każda aplikacja robi w kółko to samo: pobierz dane z serwera i pokaż jeden z trzech stanów - ładowanie, błąd albo wynik. Angular przez lata dawał na to coraz krótsze sposoby. W tej lekcji zobaczysz trzy pokolenia tego samego zadania - od ręcznej roboty po Resource API, które robi wszystko za Ciebie. Ta sama myśl, coraz mniej kodu.

Pokolenie pierwsze: jeden serwis CRUD dla wszystkiego

Zanim zajmiemy się stanami, uprzątnijmy powtarzalność. Każdy byt (samuraj, klan, miecz) potrzebuje tych samych pięciu operacji: pobierz wszystkie, pobierz jeden, utwórz, zmień, usuń. Zamiast pisać je pięć razy, tworzysz jeden generyczny serwis.

@Injectable({ providedIn: 'root' })
export class ApiService<T extends { id: number }> {
  constructor(private http: HttpClient, private baseUrl: string) {}

  getAll(): Observable<T[]> {
    return this.http.get<T[]>(this.baseUrl);
  }
  getById(id: number): Observable<T> {
    return this.http.get<T>(`${this.baseUrl}/${id}`);
  }
  create(item: Omit<T, 'id'>): Observable<T> {
    return this.http.post<T>(this.baseUrl, item);
  }
  update(item: T): Observable<T> {
    return this.http.put<T>(`${this.baseUrl}/${item.id}`, item);
  }
}

// Konkretny serwis dziedziczy komplet operacji
@Injectable({ providedIn: 'root' })
export class SamuraiApiService extends ApiService<Samurai> {
  constructor(http: HttpClient) {
    super(http, '/api/samurai');
  }
}

Sekret to <T extends { id: number }> - serwis jest generyczny, działa dla dowolnego typu, byle miał id. SamuraiApiService tylko dziedziczy i podaje swój adres, a dostaje pięć gotowych metod za darmo. Omit<T, 'id'> przy tworzeniu jest sprytny: nowy samuraj nie ma jeszcze id (nada je serwer), więc typ wprost tego zabrania.

Pokolenie drugie: trzy stany na sygnałach

Teraz sedno - pokazanie stanu. Ręcznie trzymasz trzy sygnały: dane, "trwa ładowanie" i "wystąpił błąd". Sam je przełączasz w zależności od tego, co zwróci serwer.

export class SamuraiListComponent {
  samuraiList = signal<Samurai[]>([]);
  loading = signal(true);
  error = signal<string | null>(null);

  loadSamurai(): void {
    this.loading.set(true);
    this.error.set(null);

    this.samuraiService.getAll().pipe(takeUntilDestroyed()).subscribe({
      next: (samurai) => { this.samuraiList.set(samurai); this.loading.set(false); },
      error: (err) => { this.error.set(err.message); this.loading.set(false); }
    });
  }
}

Prześledź trzy stany: przed żądaniem ustawiasz loading na true, gdy dane przyjdą (next), zapisujesz je i gasisz ładowanie, a gdy serwer zwróci błąd (error), zapisujesz komunikat i też gasisz ładowanie. W szablonie @if (loading()) / @else if (error()) / @else pokazuje właściwy widok. Działa - ale zauważ, ile ręcznego przełączania: trzy sygnały i pamiętanie, by wszędzie zgasić loading. To woła o coś prostszego.

Pokolenie trzecie: Resource API robi to za Ciebie

Angular 19 zauważył, że ten wzorzec trzech stanów jest zawsze taki sam - i zamknął go w resource(). Podajesz tylko skąd wziąć dane, a stany dostajesz gotowe.

import { resource } from '@angular/core';

export class SamuraiDetailComponent {
  id = input.required<number>();

  samuraiResource = resource({
    request: () => this.id(),
    loader: async ({ request: id }) => {
      const response = await fetch(`/api/samurai/${id}`);
      if (!response.ok) throw new Error('Failed to load');
      return response.json() as Promise<Samurai>;
    }
  });
}

Porównaj z poprzednim przykładem: zniknęły trzy ręczne sygnały. resource sam wystawia status() (loading/error/success), value() i error() - w szablonie robisz na nich @switch. Co więcej, request śledzi sygnał id: gdy id się zmieni, resource sam pobierze dane od nowa. To samo zadanie co w pokoleniu drugim, ale bez ani jednego ręcznego set.

Pamiętaj z tej lekcji nie składni, lecz kierunku: każde pobranie danych to trzy stany - ładowanie, błąd, wynik. Kiedyś przełączałeś je ręcznie trzema sygnałami, dziś resource() robi to za Ciebie. Gdy widzisz ten wzorzec, wybierasz tylko, ile chcesz napisać sam.

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy HttpParams?

  2. 2. Co oferuje funkcja resource() wprowadzona w Angularze 19?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij metodę wyszukiwania tak, by wysyłała parametry zapytania (q, page) i własny nagłówek.

  • Układanie w poziomie

    Ułóż tokeny jednej linii tworzącej parametr zapytania q:

  • Układanie w pionie

    Uporządkuj kolejne kroki ładowania danych do sygnałów w komponencie:

  • Układanie w pionie

    Uporządkuj bloki szablonu obsługującego stany resource() w kolejności: nagłówek switch, ładowanie, błąd, sukces:

  • Klikanie w kolejności

    Zbuduj wywołanie ponownie ładujące dane zasobu resource():

  • Układanie w poziomie

    Ułóż tokeny jednej linii wywołującej GET z obiektem opcji params:

  • Klikanie w kolejności

    Zbuduj początek deklaracji zasobu w komponencie (do otwarcia obiektu konfiguracji):

Przydatne artykuły