Angular 20 in 2026: Resource API, httpResource en sollicitatievragen

Angular 20 introduceert de Resource API en httpResource als signal-gebaseerde alternatieven voor handmatige HttpClient-subscriptions. Deze handleiding behandelt alle drie Resource-varianten, Zod-validatie en veelgestelde sollicitatievragen.

Angular 20 Resource API en httpResource Tutorial

Angular 20 plaatst de Resource API en httpResource centraal in signal-gebaseerde data-ophaling. Deze API's vervangen omslachtige HttpClient-subscription-patronen door reactieve primitieven die laad-, fout- en opgeloste toestanden automatisch bijhouden.

Wat is er veranderd in Angular 20?

De Resource API hernoemt request naar params en loader naar stream (voor rxResource). Statuswaarden zijn nu string-literals ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') in plaats van numerieke enums. httpResource bouwt voort op HttpClient en ondersteunt interceptors en Zod-validatie standaard.

De drie Resource-varianten in Angular 20

Angular 20 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 groot pijnpunt 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
  }
}

Een aantal details zijn hier van belang. 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.

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

Angular 20 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($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 },
      }),
  });
}

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

Angular 20 wijzigt ResourceStatus van een numeriek enum naar een string-union-type. De zes mogelijke waarden bieden gedetailleerd inzicht in de levenscyclus van de resource:

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

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

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

Voor een diepere duik in Angular Signals en hoe deze integreren met het bredere framework, behandelt de signals-module 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.

Conclusie

  • 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
  • Angular 20 hernoemt request naar params en loader naar stream bij rxResource — pas bestaande code dienovereenkomstig aan
  • Statuswaarden zijn nu string-literals ('idle', 'loading', 'resolved', 'error', 'reloading', 'local'), ter vervanging van de numerieke enums uit Angular 19
  • 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 in Angular 20 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

Tags

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

Delen

Gerelateerde artikelen