Angular 20 im Jahr 2026: Resource API, httpResource und Interviewfragen

Angular 20 führt die Resource API und httpResource als signalbasierte Alternativen zu manuellen HttpClient-Subscriptions ein. Dieser Leitfaden behandelt alle drei Resource-Varianten, Zod-Validierung und häufige Interviewfragen.

Angular 20 Resource API und httpResource Tutorial

Angular 20 stellt die Resource API und httpResource in den Mittelpunkt des signalbasierten Datenabrufs. Diese APIs ersetzen umständliche HttpClient-Subscription-Muster durch reaktive Primitiven, die Lade-, Fehler- und Ergebnis-Zustände automatisch verfolgen.

Was hat sich in Angular 20 geändert?

Die Resource API benennt request in params und loader in stream (für rxResource) um. Statuswerte sind jetzt String-Literale ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') statt numerischer Enums. httpResource baut auf HttpClient auf und unterstützt Interceptors sowie Zod-Validierung direkt ab Werk.

Die drei Resource-Varianten in Angular 20

Angular 20 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 ein wesentlicher Kritikpunkt 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.

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 in Angular 20

Angular 20 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($any($event.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

Angular 20 ändert ResourceStatus von einem numerischen Enum zu einem String-Union-Typ. Die sechs möglichen Werte bieten detaillierten Einblick in den Lebenszyklus der Resource:

| Status | Bedeutung | |---|---| | '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

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

Angular 20 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ür einen tieferen Einblick in Angular Signals und deren Integration in das breitere Framework behandelt das Signals-Modul 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.

Fazit

  • 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
  • Angular 20 benennt request in params und loader in stream bei rxResource um — bestehender Code muss entsprechend aktualisiert werden
  • Statuswerte sind jetzt String-Literale ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') und ersetzen die numerischen Enums aus Angular 19
  • 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 in Angular 20 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

Tags

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

Teilen

Verwandte Artikel