Angular 20 in 2026: Resource API, httpResource and Interview Questions
Angular 20 introduces httpResource and stabilizes the Resource API for signal-based data fetching. A hands-on tutorial covering resource(), rxResource(), httpResource(), Zod validation, and common interview questions.

Angular 20 brings the Resource API and httpResource to the forefront of signal-based data fetching. These experimental APIs replace verbose HttpClient subscription patterns with reactive primitives that track loading, error, and resolved states automatically.
The Resource API renames request to params and loader to stream (for rxResource). Status values are now string literals ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') instead of numeric enums. httpResource builds on HttpClient, supporting interceptors and Zod validation out of the box.
The Three Resource Variants in Angular 20
Angular 20 offers three ways to load asynchronous data as signals. Each targets a different use case, but all share the same reactive model: declare dependencies, define a loader, and consume the result through signals.
resource()works with Promises. Ideal when usingfetch()or any promise-based API.rxResource()works with Observables. The right choice when RxJS operators likedebounceTime,retry, orswitchMapare needed.httpResource()wraps Angular'sHttpClientdirectly. Interceptors, testing utilities, and schema validation work without extra wiring.
The key difference between httpResource and the other two: httpResource uses HttpClient under the hood, which means existing interceptors continue to work. The original resource() API bypassed HttpClient entirely, which was a major pain point in Angular 19.
Building a User Profile with resource()
The resource() function accepts a params computation and a loader function. When signals inside params change, the loader re-executes automatically.
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
}
}The abortSignal parameter allows Angular to cancel in-flight requests when userId changes before the previous request completes. This prevents race conditions without manual subscription management.
Reactive Data Fetching with httpResource
httpResource eliminates boilerplate by combining URL declaration and HTTP execution in a single call. It returns an HttpResourceRef that exposes value, isLoading, error, status, and headers as signals.
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
}
}Several details matter here. The function passed to httpResource returns a request configuration object. Angular tracks signal reads inside this function, so changing category triggers a new GET request. If a request is already in flight, Angular cancels it before starting the new one.
httpResource is designed for data fetching (GET requests). Using it for POST, PUT, or DELETE operations is unsafe because request cancellation could abort mutations mid-flight. For write operations, use HttpClient directly or wrap mutations in a service method.
Schema Validation with Zod and httpResource
API responses from external services can drift from expected shapes. The parse option on httpResource integrates schema validation libraries like Zod to catch mismatches at runtime instead of silently propagating corrupt data.
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 }
);
}When the API returns data that doesn't match OrderSchema, the resource transitions to the 'error' status. The parse function's return type also determines the TypeScript type of value(), so schema definitions serve double duty as runtime validators and type generators.
rxResource with Stream and Params in Angular 20
Angular 20 renames loader to stream and request to params in rxResource. This aligns the naming with the streaming semantics that rxResource supports. The stream function receives an Observable context and must return an Observable.
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 },
}),
});
}Unlike httpResource, rxResource gives full control over the Observable pipeline. Operators like debounceTime or retry can be chained inside stream. However, httpResource handles the most common case (single GET request) with less code.
Ready to ace your Angular interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Status Tracking: String Literals Replace Enums
Angular 20 changes ResourceStatus from a numeric enum to a string union type. The six possible values provide granular insight into the resource lifecycle:
| Status | Meaning |
|---|---|
| 'idle' | params returned undefined — no request issued |
| 'loading' | First request in progress |
| 'reloading' | Subsequent request after a previous success |
| 'resolved' | Data available in value() |
| 'error' | Request failed — error() contains the error |
| 'local' | Value set locally via .set() or .update() |
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();
},
});
}Returning undefined from params sets the status to 'idle' and prevents the loader from executing. This pattern works well for conditional data fetching — display a prompt until the user provides input, then load data.
Since Angular 20, calling value() on a resource in 'error' status throws a runtime exception. Always guard reads with hasValue() or check status() before accessing value(). This is a breaking change from Angular 19 where value() returned undefined on error.
Angular 20 httpResource Interview Questions
The Resource API is becoming a standard topic in Angular interview questions. Here are questions that test real understanding beyond surface-level familiarity with the API.
Q: What problem does httpResource solve that resource() does not?
resource() uses fetch() or any promise-based loader, bypassing Angular's HttpClient. This means interceptors (for auth tokens, logging, error handling) do not apply. httpResource uses HttpClient internally, so interceptors, testing utilities (HttpTestingController), and features like withFetch() work without additional configuration.
Q: When should rxResource be preferred over httpResource?
rxResource provides full Observable control through its stream function. Choose it when the data pipeline requires RxJS operators — debouncing search input, retrying failed requests with exponential backoff, or combining multiple streams with combineLatest. For straightforward GET requests, httpResource requires less code.
Q: How does Angular handle concurrent requests when a signal changes rapidly?
All three resource variants cancel pending requests when params produces a new value. For httpResource and resource(), the AbortSignal cancels the underlying fetch. For rxResource, Angular unsubscribes from the previous Observable. This prevents stale responses from overwriting fresh data.
Q: What is the purpose of the 'local' status?
Calling .set() or .update() on a resource changes its value locally without triggering the loader. The status transitions to 'local', indicating the current value did not come from the server. This supports optimistic UI updates — the UI reflects the change immediately while a separate mutation request runs.
Q: How does Zod integration work with httpResource?
The parse option accepts any function with the signature (data: unknown) => T. When the HTTP response arrives, httpResource passes the parsed JSON through parse before setting value(). If parse throws (e.g., ZodError), the resource transitions to 'error' status. The return type of parse determines the TypeScript type of value(), providing both runtime safety and compile-time types from a single schema definition.
For a deeper dive into Angular signals and how they integrate with the broader framework, the signals module covers computed signals, effects, and the reactivity model.
Migrating from HttpClient Subscriptions to httpResource
Existing Angular applications typically fetch data with HttpClient subscriptions inside ngOnInit or use AsyncPipe with Observables. Migrating to httpResource follows a predictable pattern:
// 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()
}The migration eliminates lifecycle management boilerplate. Subscription cleanup happens automatically when the component is destroyed. Loading and error states are built into the resource — no separate boolean flags needed.
For applications already using standalone components, the migration is straightforward: replace the HttpClient injection and subscription logic with a single httpResource declaration.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Conclusion
httpResourcereplaces manualHttpClientsubscriptions with a single reactive declaration that handles loading, error, and cancellation automatically- Use
resource()for promise-based APIs,rxResource()for Observable pipelines with RxJS operators, andhttpResource()for standard HTTP calls with interceptor support - Angular 20 renames
requesttoparamsandloadertostreaminrxResource— update existing code accordingly - Status values are now string literals (
'idle','loading','resolved','error','reloading','local'), replacing numeric enums from Angular 19 - The
parseoption onhttpResourceintegrates Zod or Valibot for runtime schema validation that also drives TypeScript types - Calling
value()on a resource in error state throws in Angular 20 — always guard withhasValue()or checkstatus()first - All resource variants cancel in-flight requests automatically when dependencies change, preventing race conditions without manual cancellation logic
Tags
Share
Related articles

Angular 19 Interview Questions: Signals, SSR and Must-Know Concepts
The most common Angular 19 interview questions: Signals, incremental hydration, zoneless change detection, and new reactive APIs with code examples and expected answers.

Angular 19 Zoneless: Performance and Change Detection Without Zone.js
Angular zoneless change detection removes Zone.js to deliver smaller bundles, faster rendering, and explicit reactivity through signals. This deep dive covers the migration path from Zone.js to zoneless Angular, from provideExperimentalZonelessChangeDetection in Angular 19 to the stable API in Angular 20+.

Top 25 Angular Interview Questions: Complete Guide to Success
The 25 most asked Angular interview questions in 2026. Detailed answers, code examples and tips to land your Angular developer position.