Angular Resource API en httpResource: Volledige gids (Stabiel in Angular 22)

Angular's Resource API en httpResource zijn stabiel sinds Angular 22. Deze gids behandelt resource(), rxResource(), httpResource(), de nieuwe chain() helper voor afhankelijke resources, Zod-validatie en veelgestelde sollicitatievragen.

Angular 20 Resource API en httpResource Tutorial

Angular's Resource API en httpResource bieden signal-gebaseerde data-ophaling die laad-, fout- en opgeloste toestanden automatisch bijhoudt. Geïntroduceerd in Angular 20, werden deze API's stabiel in Angular 22, ter vervanging van omslachtige HttpClient-subscription-patronen door reactieve primitieven.

Stabiel sinds Angular 22

De Resource API (resource(), rxResource(), httpResource()) is gepromoveerd van experimenteel naar stabiel in Angular 22 (juni 2026). De params en stream naamgeving, string literal statussen en de nieuwe chain() helper voor afhankelijke resources zijn allemaal productierijp. Bij een upgrade van Angular 20 of 21 blijft de API-interface compatibel.

De drie Resource-varianten

Angular biedt drie manieren om asynchrone data als signals te laden. Elke variant richt zich op een ander gebruiksscenario, maar ze delen allemaal hetzelfde reactieve model: afhankelijkheden declareren, een loader definiëren en het resultaat via signals consumeren.

  • resource() werkt met Promises. Ideaal bij gebruik van fetch() of een andere Promise-gebaseerde API.
  • rxResource() werkt met Observables. De juiste keuze wanneer RxJS-operators zoals debounceTime, retry of switchMap nodig zijn.
  • httpResource() omhult Angular's HttpClient rechtstreeks. Interceptors, test-utilities en schemavalidatie werken zonder extra configuratie.

Het belangrijkste verschil tussen httpResource en de andere twee: httpResource gebruikt HttpClient intern, wat betekent dat bestaande interceptors blijven werken. De oorspronkelijke resource() API omzeilde HttpClient volledig, wat een beperking was in Angular 19.

Een gebruikersprofiel bouwen met resource()

De resource()-functie accepteert een params-berekening en een loader-functie. Wanneer signals binnen params veranderen, wordt de loader automatisch opnieuw uitgevoerd.

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
  }
}

De abortSignal-parameter stelt Angular in staat om lopende verzoeken te annuleren wanneer userId verandert voordat het vorige verzoek is voltooid. Dit voorkomt race conditions zonder handmatig subscription-beheer.

Reactieve data-ophaling met httpResource

httpResource elimineert boilerplate-code door URL-declaratie en HTTP-uitvoering te combineren in één enkele aanroep. Het retourneert een HttpResourceRef dat value, isLoading, error, status en headers als signals beschikbaar stelt.

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
  }
}

De functie die aan httpResource wordt doorgegeven, retourneert een request-configuratieobject. Angular volgt signal-leesbewerkingen binnen deze functie, dus het wijzigen van category triggert een nieuw GET-verzoek. Als er al een verzoek onderweg is, annuleert Angular dit voordat het nieuwe wordt gestart.

httpResource is alleen voor leesbewerkingen

httpResource is ontworpen voor data-ophaling (GET-verzoeken). Het gebruiken voor POST-, PUT- of DELETE-operaties is onveilig, omdat het annuleren van verzoeken mutaties halverwege kan afbreken. Gebruik voor schrijfoperaties HttpClient rechtstreeks of verpak mutaties in een service-methode.

Resources koppelen met chain()

Angular 22 introduceerde de chain() helper voor het laden van afhankelijke resources. Wanneer één resource afhankelijk is van de waarde van een andere, propageert chain() automatisch laad- en foutstatussen.

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) retourneert niet alleen de waarde. Terwijl userResource laadt, rapporteert ook postsResource de laadstatus en voert zijn loader niet uit. Als userResource een fout geeft, gaat postsResource over naar foutstatus met een ResourceDependencyError die de oorspronkelijke fout bevat.

Dit vervangt het RxJS-patroon van geneste switchMap-aanroepen door expliciete, standaard correcte statuspropagatie.

Schemavalidatie met Zod en httpResource

API-responses van externe diensten kunnen afwijken van de verwachte datastructuren. De parse-optie van httpResource integreert schemavalidatiebibliotheken zoals Zod om afwijkingen tijdens runtime te detecteren in plaats van corrupte data stilzwijgend door te geven.

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 }
  );
}

Wanneer de API data retourneert die niet overeenkomt met OrderSchema, schakelt de resource over naar de 'error'-status. Het retourtype van de parse-functie bepaalt ook het TypeScript-type van value(), waardoor schemadefinities zowel als runtime-validators als type-generatoren fungeren.

rxResource met Stream en Params

Angular hernoemt loader naar stream en request naar params bij rxResource. Deze naamswijziging sluit aan bij de streaming-semantiek die rxResource ondersteunt. De stream-functie ontvangt een Observable-context en moet een Observable retourneren.

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 },
      }),
  });
}

In tegenstelling tot httpResource biedt rxResource volledige controle over de Observable-pipeline. Operators zoals debounceTime of retry kunnen binnen stream worden gekoppeld. Voor het meest voorkomende scenario (enkel GET-verzoek) vereist httpResource echter minder code.

Klaar om je Angular gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Statustracking: string-literals vervangen enums

ResourceStatus is een string-union-type. De zes mogelijke waarden bieden gedetailleerd inzicht in de levenscyclus van de resource:

StatusBetekenis
'idle'params retourneerde undefined, geen verzoek verzonden
'loading'Eerste verzoek in uitvoering
'reloading'Volgend verzoek na een eerder succes
'resolved'Data beschikbaar in value()
'error'Verzoek mislukt, error() bevat de fout
'local'Waarde lokaal ingesteld via .set() of .update()
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();
    },
  });
}

Het retourneren van undefined vanuit params zet de status op 'idle' en voorkomt dat de loader wordt uitgevoerd. Dit patroon werkt goed voor voorwaardelijke data-ophaling: een prompt tonen totdat de gebruiker invoer geeft, en dan de data laden.

Toegang tot value() in foutstatus

Het aanroepen van value() op een resource in 'error'-status gooit een runtime-exceptie. Leesbewerkingen moeten altijd worden beschermd met hasValue() of door status() te controleren voordat value() wordt benaderd. Dit is een wijziging ten opzichte van Angular 19, waar value() bij fouten undefined retourneerde.

Angular httpResource sollicitatievragen

De Resource API wordt steeds vaker een standaardonderwerp bij Angular-sollicitatievragen. De volgende vragen toetsen een echt begrip dat verder gaat dan oppervlakkige bekendheid met de API.

V: Welk probleem lost httpResource op dat resource() niet oplost?

resource() gebruikt fetch() of een andere Promise-gebaseerde loader en omzeilt daarbij Angular's HttpClient. Dit betekent dat interceptors (voor auth-tokens, logging, foutafhandeling) niet worden toegepast. httpResource gebruikt intern HttpClient, waardoor interceptors, test-utilities (HttpTestingController) en functies zoals withFetch() zonder extra configuratie werken.

V: Wanneer heeft rxResource de voorkeur boven httpResource?

rxResource biedt volledige Observable-controle via de stream-functie. De keuze valt op rxResource wanneer de datapipeline RxJS-operators vereist: debouncing van zoekinvoer, herhaalpogingen met exponentieel backoff of het combineren van meerdere streams met combineLatest. Voor eenvoudige GET-verzoeken vereist httpResource minder code.

V: Hoe gaat Angular om met gelijktijdige verzoeken wanneer een signal snel verandert?

Alle drie de Resource-varianten annuleren lopende verzoeken wanneer params een nieuwe waarde produceert. Bij httpResource en resource() annuleert het AbortSignal het onderliggende fetch-verzoek. Bij rxResource meldt Angular zich af van het vorige Observable. Dit voorkomt dat verouderde responses actuele data overschrijven.

V: Wat is het doel van de 'local'-status?

Het aanroepen van .set() of .update() op een resource wijzigt de waarde lokaal zonder de loader te activeren. De status gaat over naar 'local', wat aangeeft dat de huidige waarde niet van de server afkomstig is. Dit ondersteunt optimistische UI-updates: de interface weerspiegelt de wijziging onmiddellijk terwijl een apart mutatieverzoek parallel wordt uitgevoerd.

V: Hoe werkt de Zod-integratie met httpResource?

De parse-optie accepteert elke functie met de signatuur (data: unknown) => T. Wanneer de HTTP-response binnenkomt, stuurt httpResource de geparseerde JSON door parse voordat value() wordt ingesteld. Als parse een fout gooit (bijv. ZodError), gaat de resource over naar de 'error'-status. Het retourtype van parse bepaalt het TypeScript-type van value(), wat zowel runtime-veiligheid als compilatietijdtypes oplevert vanuit één enkele schemadefinitie.

V: Hoe gaat chain() om met fouten van upstream resources?

Bij het gebruik van chain(upstreamResource) in de params-functie van een resource, voert de afhankelijke resource zijn loader niet uit als de upstream resource aan het laden is, idle is of in foutstatus staat. Als de upstream resource een fout geeft, gaat de afhankelijke resource over naar foutstatus met een ResourceDependencyError. Deze fout bevat de dependency (de upstream resource) en cause (de daadwerkelijke upstream fout), waardoor onderscheid gemaakt kan worden tussen "mijn verzoek is mislukt" en "het verzoek van mijn afhankelijkheid is mislukt".

Voor een diepgaandere behandeling van Angular Signals en hoe deze integreren met het bredere framework, behandelt de signals-gids berekende signals, effects en het reactiviteitsmodel.

Migratie van HttpClient-subscriptions naar httpResource

Bestaande Angular-applicaties halen data doorgaans op met HttpClient-subscriptions in ngOnInit of gebruiken AsyncPipe met Observables. De migratie naar httpResource volgt een voorspelbaar patroon:

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()
}

De migratie elimineert alle lifecycle-management boilerplate. Het opschonen van subscriptions gebeurt automatisch wanneer het component wordt vernietigd. Laad- en fouttoestanden zijn ingebouwd in de resource, aparte boolean-vlaggen zijn niet meer nodig.

Voor applicaties die al Standalone Components gebruiken, is de migratie eenvoudig: vervang de HttpClient-injectie en subscription-logica door één enkele httpResource-declaratie.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Bronnen

Belangrijkste punten over Angular Resource API

  • httpResource vervangt handmatige HttpClient-subscriptions door één enkele reactieve declaratie die laden, fouten en annulering automatisch afhandelt
  • Gebruik resource() voor Promise-gebaseerde API's, rxResource() voor Observable-pipelines met RxJS-operators en httpResource() voor standaard HTTP-aanroepen met interceptor-ondersteuning
  • De chain() helper (Angular 22+) maakt het laden van afhankelijke resources mogelijk met automatische statuspropagatie; als de upstream faalt, krijgt de afhankelijke een ResourceDependencyError
  • Statuswaarden zijn string-literals ('idle', 'loading', 'resolved', 'error', 'reloading', 'local')
  • De parse-optie van httpResource integreert Zod of Valibot voor runtime-schemavalidatie die tevens TypeScript-types genereert
  • Het aanroepen van value() op een resource in foutstatus gooit een exceptie; altijd beschermen met hasValue() of status() vooraf controleren
  • Alle Resource-varianten annuleren lopende verzoeken automatisch wanneer afhankelijkheden veranderen, waardoor race conditions worden voorkomen zonder handmatige annuleringslogica
Dagelijkse challenge

Zie jij de bug in Angular?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 13 oktober 2026

Tags

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

Delen

Gerelateerde artikelen