Angular Resource API und httpResource: Vollständiger Guide (Stabil in Angular 22)

Die Angular Resource API und httpResource sind seit Angular 22 stabil. Dieser Guide behandelt resource(), rxResource(), httpResource(), den neuen chain()-Helper für abhängige Resources, Zod-Validierung und häufige Interviewfragen.

Angular 20 Resource API und httpResource Tutorial

Angular bietet mit der Resource API und httpResource signalbasierte Datenabruf-Funktionen, die Lade-, Fehler- und Ergebnis-Zustände automatisch verfolgen. In Angular 20 eingeführt, wurden diese APIs in Angular 22 stabil und ersetzen umständliche HttpClient-Subscription-Muster durch reaktive Primitiven.

Stabil seit Angular 22

Die Resource API (resource(), rxResource(), httpResource()) ist seit Angular 22 (Juni 2026) stabil. Die Benennung params und stream, String-Literal-Status und der neue chain()-Helper für abhängige Resources sind produktionsreif. Bei einem Upgrade von Angular 20 oder 21 bleibt die API-Oberfläche kompatibel.

Die drei Resource-Varianten

Angular bietet drei Wege, um asynchrone Daten als Signals zu laden. Jede Variante zielt auf einen anderen Anwendungsfall ab, aber alle teilen dasselbe reaktive Modell: Abhängigkeiten deklarieren, einen Loader definieren und das Ergebnis über Signals konsumieren.

  • resource() arbeitet mit Promises. Ideal bei Verwendung von fetch() oder jeder Promise-basierten API.
  • rxResource() arbeitet mit Observables. Die richtige Wahl, wenn RxJS-Operatoren wie debounceTime, retry oder switchMap benötigt werden.
  • httpResource() umschließt Angulars HttpClient direkt. Interceptors, Test-Utilities und Schema-Validierung funktionieren ohne zusätzliche Konfiguration.

Der entscheidende Unterschied zwischen httpResource und den anderen beiden: httpResource nutzt HttpClient intern, was bedeutet, dass bestehende Interceptors weiterhin funktionieren. Die ursprüngliche resource()-API umging HttpClient vollständig, was in Angular 19 eine Einschränkung war.

Ein Benutzerprofil mit resource() erstellen

Die resource()-Funktion akzeptiert eine params-Berechnung und eine loader-Funktion. Wenn sich Signals innerhalb von params ändern, wird der Loader automatisch erneut ausgeführt.

user-profile.component.tstypescript
import { Component, signal, resource } from '@angular/core';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-profile',
  template: \`
    @if (userResource.hasValue()) {
      <h2>{{ userResource.value().name }}</h2>
      <p>{{ userResource.value().email }}</p>
    } @else if (userResource.isLoading()) {
      <p>Loading profile...</p>
    } @else if (userResource.error()) {
      <p>Failed to load user</p>
    }
  \`,
})
export class UserProfileComponent {
  userId = signal(1);

  // params produces the reactive dependency
  // loader receives it and returns a Promise
  userResource = resource<User, number>({
    params: () => this.userId(),
    loader: async ({ params: id, abortSignal }) => {
      const res = await fetch(\`/api/users/\${id}\`, { signal: abortSignal });
      return res.json();
    },
  });

  loadUser(id: number) {
    this.userId.set(id); // triggers automatic refetch
  }
}

Der abortSignal-Parameter ermöglicht es Angular, laufende Anfragen abzubrechen, wenn sich userId ändert, bevor die vorherige Anfrage abgeschlossen ist. Das verhindert Race Conditions ohne manuelles Subscription-Management.

Reaktiver Datenabruf mit httpResource

httpResource eliminiert Boilerplate-Code, indem URL-Deklaration und HTTP-Ausführung in einem einzigen Aufruf kombiniert werden. Es gibt ein HttpResourceRef zurück, das value, isLoading, error, status und headers als Signals bereitstellt.

product-list.component.tstypescript
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

@Component({
  selector: 'app-product-list',
  template: \`
    @if (products.hasValue()) {
      @for (product of products.value(); track product.id) {
        <div class="product-card">
          <h3>{{ product.name }}</h3>
          <span>{{ product.price | currency }}</span>
        </div>
      }
    } @else if (products.isLoading()) {
      <p>Loading products...</p>
    }
  \`,
})
export class ProductListComponent {
  category = signal('electronics');

  // httpResource re-fetches whenever category() changes
  products = httpResource<Product[]>(() => ({
    url: '/api/products',
    params: { category: this.category() },
  }));

  filterByCategory(cat: string) {
    this.category.set(cat); // pending request is cancelled, new one starts
  }
}

Die an httpResource übergebene Funktion gibt ein Request-Konfigurationsobjekt zurück. Angular verfolgt Signal-Lesevorgänge innerhalb dieser Funktion, sodass eine Änderung von category eine neue GET-Anfrage auslöst. Befindet sich bereits eine Anfrage im Flug, bricht Angular diese ab, bevor die neue gestartet wird.

httpResource ist nur für Lesezugriffe

httpResource ist für den Datenabruf (GET-Anfragen) konzipiert. Die Verwendung für POST-, PUT- oder DELETE-Operationen ist unsicher, da das Abbrechen von Anfragen Mutationen mitten in der Ausführung unterbrechen könnte. Für Schreiboperationen sollte HttpClient direkt verwendet oder Mutationen in eine Service-Methode eingebettet werden.

Verkettung von Resources mit chain()

Angular 22 führte den chain()-Helper für abhängige Resource-Ladevorgänge ein. Wenn eine Resource vom Wert einer anderen abhängt, propagiert chain() automatisch Lade- und Fehlerstatus.

user-posts.component.tstypescript
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface User {
  id: number;
  name: string;
  authorId: string;
}

interface Post {
  id: number;
  title: string;
  content: string;
}

@Component({
  selector: 'app-user-posts',
  template: \`
    @if (postsResource.isLoading()) {
      <p>Loading...</p>
    } @else if (postsResource.hasValue()) {
      <h2>Posts by {{ userResource.value().name }}</h2>
      @for (post of postsResource.value(); track post.id) {
        <article>
          <h3>{{ post.title }}</h3>
        </article>
      }
    } @else if (postsResource.error()) {
      <p>Failed to load posts</p>
    }
  \`,
})
export class UserPostsComponent {
  userId = signal(1);

  userResource = httpResource<User>(() => \`/api/users/\${this.userId()}\`);

  // chain() propagates userResource's status to postsResource
  postsResource = httpResource<Post[]>({
    params: ({ chain }) => {
      const user = chain(this.userResource);
      return { url: '/api/posts', params: { authorId: user.authorId } };
    },
  });
}

chain(userResource) gibt nicht nur den Wert zurück. Während userResource lädt, meldet auch postsResource den Ladezustand und führt seinen Loader nicht aus. Wenn userResource einen Fehler hat, wechselt postsResource in den Fehlerzustand mit einem ResourceDependencyError, der den Ursprungsfehler enthält.

Dies ersetzt das RxJS-Muster verschachtelter switchMap-Aufrufe durch explizite, standardmäßig korrekte Statuspropagation.

Schema-Validierung mit Zod und httpResource

API-Antworten externer Dienste können von den erwarteten Datenstrukturen abweichen. Die parse-Option von httpResource integriert Schema-Validierungsbibliotheken wie Zod, um Abweichungen zur Laufzeit zu erkennen, statt fehlerhafte Daten stillschweigend weiterzugeben.

order.component.tstypescript
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { z } from 'zod';

// Define the expected shape with Zod
const OrderSchema = z.object({
  id: z.number(),
  status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
  total: z.number().positive(),
  items: z.array(z.object({
    productId: z.number(),
    quantity: z.number().int().positive(),
    unitPrice: z.number().positive(),
  })),
  createdAt: z.string().datetime(),
});

type Order = z.infer<typeof OrderSchema>;

@Component({
  selector: 'app-order',
  template: \`
    @if (order.hasValue()) {
      <h2>Order #{{ order.value().id }}</h2>
      <p>Status: {{ order.value().status }}</p>
      <p>Total: {{ order.value().total | currency }}</p>
    } @else if (order.error()) {
      <p>Invalid order data received</p>
    }
  \`,
})
export class OrderComponent {
  orderId = signal(42);

  // parse validates the response before exposing it as a signal
  order = httpResource<Order>(
    () => \`/api/orders/\${this.orderId()}\`,
    { parse: OrderSchema.parse }
  );
}

Wenn die API Daten zurückgibt, die nicht zum OrderSchema passen, wechselt die Resource in den 'error'-Status. Der Rückgabetyp der parse-Funktion bestimmt auch den TypeScript-Typ von value(), sodass Schema-Definitionen gleichzeitig als Laufzeit-Validatoren und Typ-Generatoren dienen.

rxResource mit Stream und Params

Angular benennt loader in stream und request in params bei rxResource um. Diese Namensänderung orientiert sich an der Streaming-Semantik, die rxResource unterstützt. Die stream-Funktion erhält einen Observable-Kontext und muss ein Observable zurückgeben.

search.component.tstypescript
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { debounceTime, switchMap } from 'rxjs';

interface SearchResult {
  id: number;
  title: string;
  excerpt: string;
}

@Component({
  selector: 'app-search',
  template: \`
    <input (input)="query.set((.target).value)" placeholder="Search..." />
    @if (results.isLoading()) {
      <p>Searching...</p>
    }
    @if (results.hasValue()) {
      @for (item of results.value(); track item.id) {
        <div>{{ item.title }}</div>
      }
    }
  \`,
})
export class SearchComponent {
  private http = inject(HttpClient);
  query = signal('');

  // params (was "request") provides the reactive input
  // stream (was "loader") returns an Observable
  results = rxResource<SearchResult[], string>({
    params: () => this.query(),
    stream: ({ params: q }) =>
      this.http.get<SearchResult[]>('/api/search', {
        params: { q },
      }),
  });
}

Im Gegensatz zu httpResource bietet rxResource volle Kontrolle über die Observable-Pipeline. Operatoren wie debounceTime oder retry können innerhalb von stream verkettet werden. Für den häufigsten Fall (einzelne GET-Anfrage) erfordert httpResource jedoch weniger Code.

Bereit für deine Angular-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Status-Tracking: String-Literale ersetzen Enums

ResourceStatus ist ein String-Union-Typ. Die sechs möglichen Werte bieten detaillierten Einblick in den Lebenszyklus der Resource:

StatusBedeutung
'idle'params hat undefined zurückgegeben, keine Anfrage gesendet
'loading'Erste Anfrage läuft
'reloading'Folge-Anfrage nach vorherigem Erfolg
'resolved'Daten in value() verfügbar
'error'Anfrage fehlgeschlagen, error() enthält den Fehler
'local'Wert lokal über .set() oder .update() gesetzt
status-demo.component.tstypescript
import { Component, signal, resource } from '@angular/core';

@Component({
  selector: 'app-status-demo',
  template: \`
    <p>Status: {{ data.status() }}</p>
    @switch (data.status()) {
      @case ('loading') { <spinner /> }
      @case ('reloading') { <subtle-spinner /> }
      @case ('resolved') { <data-table [rows]="data.value()" /> }
      @case ('error') { <error-banner [error]="data.error()" /> }
      @case ('idle') { <p>Select a filter to load data</p> }
    }
  \`,
})
export class StatusDemoComponent {
  filter = signal<string | undefined>(undefined);

  data = resource({
    params: () => this.filter(),
    loader: async ({ params: f, abortSignal }) => {
      const res = await fetch(\`/api/data?filter=\${f}\`, { signal: abortSignal });
      return res.json();
    },
  });
}

Die Rückgabe von undefined aus params setzt den Status auf 'idle' und verhindert die Ausführung des Loaders. Dieses Muster eignet sich gut für bedingten Datenabruf: eine Aufforderung anzeigen, bis der Benutzer eine Eingabe macht, und dann die Daten laden.

Zugriff auf value() im Fehlerstatus

Der Aufruf von value() auf einer Resource im 'error'-Status löst eine Laufzeit-Exception aus. Lesezugriffe sollten immer mit hasValue() abgesichert oder status() vor dem Zugriff auf value() geprüft werden. Dies ist eine Änderung gegenüber Angular 19, wo value() bei Fehlern undefined zurückgab.

Angular httpResource Interviewfragen

Die Resource API wird zunehmend zum Standardthema bei Angular-Interviewfragen. Die folgenden Fragen prüfen echtes Verständnis jenseits oberflächlicher Vertrautheit mit der API.

F: Welches Problem löst httpResource, das resource() nicht löst?

resource() verwendet fetch() oder einen beliebigen Promise-basierten Loader und umgeht dabei Angulars HttpClient. Das bedeutet, dass Interceptors (für Auth-Tokens, Logging, Fehlerbehandlung) nicht greifen. httpResource nutzt intern HttpClient, sodass Interceptors, Test-Utilities (HttpTestingController) und Features wie withFetch() ohne zusätzliche Konfiguration funktionieren.

F: Wann sollte rxResource gegenüber httpResource bevorzugt werden?

rxResource bietet volle Observable-Kontrolle über seine stream-Funktion. Die Wahl fällt auf rxResource, wenn die Daten-Pipeline RxJS-Operatoren erfordert: Debouncing von Sucheingaben, Wiederholungsversuche mit exponentiellem Backoff oder die Kombination mehrerer Streams mit combineLatest. Für einfache GET-Anfragen benötigt httpResource weniger Code.

F: Wie geht Angular mit gleichzeitigen Anfragen um, wenn sich ein Signal schnell ändert?

Alle drei Resource-Varianten brechen laufende Anfragen ab, wenn params einen neuen Wert erzeugt. Bei httpResource und resource() bricht das AbortSignal den zugrundeliegenden Fetch ab. Bei rxResource kündigt Angular das vorherige Observable-Abonnement. Dies verhindert, dass veraltete Antworten aktuelle Daten überschreiben.

F: Wozu dient der 'local'-Status?

Der Aufruf von .set() oder .update() auf einer Resource ändert ihren Wert lokal, ohne den Loader auszulösen. Der Status wechselt zu 'local', was anzeigt, dass der aktuelle Wert nicht vom Server stammt. Dies unterstützt optimistische UI-Updates: die Oberfläche spiegelt die Änderung sofort wider, während eine separate Mutations-Anfrage parallel läuft.

F: Wie funktioniert die Zod-Integration mit httpResource?

Die parse-Option akzeptiert jede Funktion mit der Signatur (data: unknown) => T. Wenn die HTTP-Antwort eintrifft, leitet httpResource das geparste JSON durch parse, bevor value() gesetzt wird. Wenn parse einen Fehler wirft (z.B. ZodError), wechselt die Resource in den 'error'-Status. Der Rückgabetyp von parse bestimmt den TypeScript-Typ von value() und bietet so sowohl Laufzeitsicherheit als auch Compile-Zeit-Typen aus einer einzigen Schema-Definition.

F: Wie behandelt chain() Fehler von vorgelagerten Resources?

Bei Verwendung von chain(upstreamResource) in der params-Funktion einer Resource führt die abhängige Resource ihren Loader nicht aus, wenn die vorgelagerte Resource lädt, idle ist oder sich im Fehlerzustand befindet. Wenn die vorgelagerte Resource einen Fehler hat, wechselt die abhängige Resource in den Fehlerzustand mit einem ResourceDependencyError. Dieser Fehler enthält die dependency (die vorgelagerte Resource) und cause (den eigentlichen Ursprungsfehler), sodass zwischen "meine Anfrage ist fehlgeschlagen" und "die Anfrage meiner Abhängigkeit ist fehlgeschlagen" unterschieden werden kann.

Für einen tieferen Einblick in Angular Signals und deren Integration in das breitere Framework behandelt der Signals-Guide berechnete Signals, Effects und das Reaktivitätsmodell.

Migration von HttpClient-Subscriptions zu httpResource

Bestehende Angular-Anwendungen rufen Daten typischerweise mit HttpClient-Subscriptions in ngOnInit ab oder verwenden AsyncPipe mit Observables. Die Migration zu httpResource folgt einem vorhersehbaren Muster:

typescript
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
  private http = inject(HttpClient);
  private destroy$ = new Subject<void>();
  users: User[] = [];
  loading = false;
  error: string | null = null;

  ngOnInit() {
    this.loading = true;
    this.http.get<User[]>('/api/users')
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (data) => { this.users = data; this.loading = false; },
        error: (err) => { this.error = err.message; this.loading = false; },
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

// AFTER: httpResource handles lifecycle automatically
@Component({ /* ... */ })
export class AfterComponent {
  users = httpResource<User[]>(() => '/api/users');
  // No ngOnInit, no Subject, no manual unsubscribe
  // Template uses users.value(), users.isLoading(), users.error()
}

Die Migration beseitigt den gesamten Lifecycle-Management-Boilerplate. Die Bereinigung von Subscriptions erfolgt automatisch, wenn die Komponente zerstört wird. Lade- und Fehlerzustände sind in die Resource integriert, separate Boolean-Flags sind nicht mehr nötig.

Für Anwendungen, die bereits Standalone Components verwenden, ist die Migration unkompliziert: Die HttpClient-Injektion und Subscription-Logik wird durch eine einzige httpResource-Deklaration ersetzt.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Quellen

Das Wichtigste zur Angular Resource API

  • httpResource ersetzt manuelle HttpClient-Subscriptions durch eine einzige reaktive Deklaration, die Laden, Fehler und Abbruch automatisch handhabt
  • resource() eignet sich für Promise-basierte APIs, rxResource() für Observable-Pipelines mit RxJS-Operatoren und httpResource() für Standard-HTTP-Aufrufe mit Interceptor-Unterstützung
  • Der chain()-Helper (Angular 22+) ermöglicht abhängiges Resource-Laden mit automatischer Statuspropagation; wenn die vorgelagerte Resource fehlschlägt, erhält die abhängige einen ResourceDependencyError
  • Statuswerte sind String-Literale ('idle', 'loading', 'resolved', 'error', 'reloading', 'local')
  • Die parse-Option von httpResource integriert Zod oder Valibot für Laufzeit-Schema-Validierung, die gleichzeitig TypeScript-Typen ableitet
  • Der Aufruf von value() auf einer Resource im Fehlerstatus wirft eine Exception; immer mit hasValue() absichern oder status() vorab prüfen
  • Alle Resource-Varianten brechen laufende Anfragen automatisch ab, wenn sich Abhängigkeiten ändern, und verhindern so Race Conditions ohne manuelle Abbruchlogik
Tägliche Challenge

Findest du den Bug in Angular?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 13. Oktober 2026

Tags

#angular
#typescript
#resource-api
#httpresource
#signals

Teilen

Verwandte Artikel