Munsif.
AboutExperienceProjectsAchievementsBlogsContact
HomeAboutExperienceProjectsAchievementsBlogsContact
Munsif.

Frontend Developer crafting scalable web applications with modern technologies and clean code practices.

Quick Links

  • About
  • Experience
  • Projects
  • Achievements
  • Blogs
  • Contact

Connect

© 2026 Shaik Munsif. All rights reserved.

Built with Next.js & Tailwind

0%
Welcome back!Continue where you left off
Back to Blogs
Angular

Angular Resource API: Master resource(), rxResource() & httpResource() from Scratch

Learn Angular's new reactive data-fetching APIs: resource(), rxResource(), and httpResource(). Discover how they replace traditional HTTP/Signal fetching, handle reloading and race conditions, and see real-world scenarios.

Jun 16, 202618 min read
AngularSignalsReactivityHTTPModern AngularRxJS
Angular Modern FeaturesPart 4 of 4
  • 1. Angular Signals: The Complete Guide from Beginner to Advanced
  • 2. Angular Without Zone.js: Signals, New Control Flow & Zoneless Change Detection
  • 3. Angular Signal Forms: The In-Depth 'Zero to Hero' Guide
  • 4. Angular Resource API: Master resource(), rxResource() & httpResource() from Scratch

Introduction

Remember building custom async wrappers on top of signals just to fetch data? If you followed our Angular Signals Guide — particularly the Custom Signal Utilities section — you know how much glue code that requires.

Good news: you can delete all of that now. With Angular 19+, Angular introduces the Resource API — three purpose-built primitives (resource(), rxResource(), and httpResource()) that handle async data fetching, loading states, errors, cancellation, and re-evaluation out of the box.

In this guide, we'll dissect each variant, explore how they manage requests under the hood, and build three production-ready, real-world examples you can drop into your apps today.


Why the Resource API? 💡

Before the Resource API, fetching data in Angular required writing boilerplate code. Whether using RxJS HttpClient or Promises, you had to manually track states:

typescript
// ❌ The old, manual way
@Component({...})
export class UserProfile {
  userId = signal(1);
  user = signal<User | null>(null);
  isLoading = signal(false);
  error = signal<string | null>(null);

  constructor(private http: HttpClient) {
    effect(() => {
      this.isLoading.set(true);
      this.http.get<User>(`/api/users/${this.userId()}`).subscribe({
        next: (u) => {
          this.user.set(u);
          this.isLoading.set(false);
        },
        error: (err) => {
          this.error.set(err.message);
          this.isLoading.set(false);
        }
      });
    });
  }
}

This approach suffers from race conditions (if userId changes rapidly, multiple subscriptions run concurrently and can resolve out of order) and requires verbose state management.

The Resource API solves this declaratively:

typescript
// ✅ The modern, declarative Resource way
@Component({...})
export class UserProfile {
  userId = signal(1);
  
  userResource = httpResource(() => `/api/users/${this.userId()}`);
  
  // Exposes read-only signals:
  // userResource.value() -> User | undefined
  // userResource.isLoading() -> boolean
  // userResource.error() -> unknown
}

Anatomy of a Resource 🧬

A resource wraps an asynchronous data source and exposes it as a collection of read-only signals.

The State Signals

When you define a resource, you get an object containing the following signals:

  1. value(): A read-only signal containing the latest resolved value, or undefined if the resource has not yet resolved (or has errored).
  2. isLoading(): A read-only boolean signal indicating if a request is currently in progress.
  3. error(): A read-only signal containing the error thrown during the last fetch, or undefined if no error occurred.
  4. status(): A read-only signal indicating the detailed state of the resource.

The Status Enum

The status() signal returns a value from the ResourceStatus enum:

Status ValueMeaning
ResourceStatus.IdleThe resource has been created but loader hasn't started (e.g., if parameter signal is undefined or null).
ResourceStatus.LoadingThe resource is loading its initial value.
ResourceStatus.ResolvedThe resource loader resolved successfully and value() contains the data.
ResourceStatus.ErrorThe resource loader threw an error.
ResourceStatus.ReloadingThe resource already has a resolved value, but a parameter change or manual reload has triggered a new request.

Deep Dive: The Three Resource APIs 🔍

Angular provides three helper functions to create resources, depending on the underlying async mechanism.

1. resource() - Promise-Based Async Tasks

The core resource() function is ideal for general-purpose asynchronous operations, such as interacting with browser Web APIs (e.g., Geolocation, Clipboard), using native fetch, or wrapping third-party Promise-based libraries.

typescript
import { resource } from '@angular/core';

const userResource = resource({
  // The request parameter function. Any signals read here will trigger a re-fetch.
  request: () => ({ id: this.userId() }),
  
  // The loader function. It receives the request and returns a Promise.
  loader: async ({ request, abortSignal }) => {
    const response = await fetch(`/api/users/${request.id}`, { signal: abortSignal });
    if (!response.ok) throw new Error('Failed to fetch user');
    return response.json();
  }
});

Note: Angular automatically supplies an abortSignal inside the loader context. If a new request is triggered before the current one finishes, the previous request is aborted automatically.

2. rxResource() - The RxJS Observable Bridge

If you are working with an existing service layer built on RxJS Observables, or if you need to use RxJS operators (like debounceTime, distinctUntilChanged, or custom retry logic), rxResource is your best friend. It bridges the gap between Observables and Signals.

rxResource lives in @angular/core/rxjs-interop:

typescript
import { rxResource } from '@angular/core/rxjs-interop';

const searchResults = rxResource({
  request: () => ({ query: this.searchQuery() }),
  loader: ({ request }) => {
    // Return an Observable. rxResource automatically subscribes/unsubscribes.
    return this.searchService.getResults(request.query);
  }
});

3. httpResource() - Declarative HttpClient Wrapper

Introduced to work seamlessly with Angular's HttpClient, httpResource is the cleanest way to make HTTP GET requests. It automatically uses the configured HttpClient (including interceptors) and supports parameterization out of the box.

typescript
import { httpResource } from '@angular/common/http';

// Automatically maps to a GET request and resolves the response body
const weatherResource = httpResource(() => `/api/weather?city=${this.city()}`);

You can also pass options for advanced request customization:

typescript
const detailedUser = httpResource({
  request: () => ({ id: this.userId() }),
  loader: ({ request }) => ({
    url: `/api/users/${request.id}`,
    options: {
      headers: { 'Authorization': `Bearer ${this.token()}` }
    }
  })
});

Triggering Re-evaluation & Parameterization ⚙️

Resources are automatically reactive. This reactivity is governed by the request function.

Reacting to Parameter Signals

If the request function reads any signals, the resource will set its dependencies. When those signals change, the resource automatically schedules a re-fetch.

typescript
// 1. Define a dependency signal
page = signal(1);
pageSize = signal(10);

// 2. Resource automatically tracks page() and pageSize()
products = httpResource(() => `/api/products?page=${this.page()}&size=${this.pageSize()}`);

If the request returns undefined (or a falsy value depending on your checks), the resource loader will not execute, putting the resource into an Idle state. This is highly useful for conditional fetching:

typescript
userId = signal<number | null>(null);

userProfile = httpResource(() => {
  const id = this.userId();
  // If no ID is available, return undefined to skip fetching
  if (!id) return undefined;
  return `/api/users/${id}`;
});

Manual Reloading via reload()

Sometimes you need to refresh data without changing the request parameters (e.g., a "Pull to Refresh" or after mutating data on the server). Every resource exposes a .reload() method:

typescript
// Trigger a manual reload
refreshData() {
  this.products.reload();
}

During a reload, isLoading() is set to true, status() shifts to ResourceStatus.Reloading, but value() retains its previous value. This prevents layout thrashing by keeping old data visible until the new data arrives.


Real-Time Examples & Code Walkthroughs 🛠️

Let's look at three real-world, code-dense scenarios implemented with the Resource API.

Scenario 1: Auto-paginating Search Grid (httpResource)

Here we build a paginated table component. It automatically fetches new data when pagination changes or the user searches.

typescript
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';

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

interface ProductResponse {
  data: Product[];
  total: number;
}

@Component({
  selector: 'app-product-grid',
  standalone: true,
  template: `
    <div class="product-container">
      <div class="actions">
        <input 
          [value]="query()" 
          (input)="updateQuery($event)" 
          placeholder="Search products..." 
        />
        <button (click)="productsResource.reload()">Refresh 🔄</button>
      </div>

      @if (productsResource.isLoading()) {
        <div class="skeleton">Loading products...</div>
      }

      @if (productsResource.error()) {
        <div class="error-banner">
          Failed to load products. Reason: {{ getErrorMessage() }}
        </div>
      }

      @if (productsResource.value(); as response) {
        <table>
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Price</th>
            </tr>
          </thead>
          <tbody>
            @for (product of response.data; track product.id) {
              <tr>
                <td>{{ product.id }}</td>
                <td>{{ product.name }}</td>
                <td>{{ product.price | currency }}</td>
              </tr>
            } @empty {
              <tr><td colspan="3">No products found.</td></tr>
            }
          </tbody>
        </table>

        <div class="pagination">
          <button [disabled]="page() === 1" (click)="prevPage()">Prev</button>
          <span>Page {{ page() }}</span>
          <button 
            [disabled]="page() * 10 >= response.total" 
            (click)="nextPage()"
          >
            Next
          </button>
        </div>
      }
    </div>
  `
})
export class ProductGridComponent {
  query = signal('');
  page = signal(1);

  // Declarative resource
  productsResource = httpResource<ProductResponse>(() => {
    const search = this.query();
    const currentPage = this.page();
    return `/api/products?q=${search}&page=${currentPage}&limit=10`;
  });

  updateQuery(event: Event) {
    const input = event.target as HTMLInputElement;
    this.query.set(input.value);
    this.page.set(1); // Reset page on new search
  }

  nextPage() {
    this.page.update(p => p + 1);
  }

  prevPage() {
    this.page.update(p => Math.max(1, p - 1));
  }

  getErrorMessage(): string {
    const err = this.productsResource.error();
    return err instanceof Error ? err.message : 'Unknown error';
  }
}

Scenario 2: Debounced API Query with rxResource

If you want to debounce rapid keystrokes to prevent spamming the backend, we can pair rxResource with RxJS operators:

typescript
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap, of } from 'rxjs';

@Component({
  selector: 'app-user-search',
  standalone: true,
  template: `
    <div class="search-box">
      <input 
        [value]="searchVal()" 
        (input)="searchVal.set($any($event.target).value)" 
        placeholder="Type a username..." 
      />

      @if (searchResource.isLoading()) {
        <p>Searching database...</p>
      }

      @if (searchResource.value(); as users) {
        <ul>
          @for (user of users; track user.id) {
            <li>{{ user.name }} ({{ user.email }})</li>
          } @empty {
            <li>No users found.</li>
          }
        </ul>
      }
    </div>
  `
})
export class UserSearchComponent {
  searchVal = signal('');

  searchResource = rxResource({
    // Return parameters as a signal object
    request: () => ({ term: this.searchVal() }),
    loader: ({ request }) => {
      if (!request.term.trim()) return of([]); // Return empty array immediately
      
      // Debounce and call API
      return of(request.term).pipe(
        debounceTime(300),
        distinctUntilChanged(),
        switchMap(term => this.http.get<any[]>(`/api/users?search=${term}`))
      );
    }
  });

  constructor(private http: HttpClient) {}
}

Scenario 3: Promise-based Geolocation & Weather (resource)

This example utilizes the base resource() function to first get the user's browser Geolocation coordinates via a Promise, and then feed those coordinates into a weather API search.

typescript
import { Component, signal, computed } from '@angular/core';
import { resource, httpResource } from '@angular/core';

interface Coords {
  lat: number;
  lng: number;
}

@Component({
  selector: 'app-local-weather',
  standalone: true,
  template: `
    <div class="weather-card">
      <h2>Local Weather</h2>

      @if (locationResource.isLoading()) {
        <p>Requesting GPS permissions...</p>
      } @else if (locationResource.error()) {
        <p>Location access denied or unavailable.</p>
      }

      @if (weatherResource.isLoading()) {
        <p>Fetching forecast for coordinates...</p>
      }

      @if (weatherResource.value(); as weather) {
        <div class="weather-data">
          <h3>{{ weather.locationName }}</h3>
          <p class="temp">{{ weather.temperature }}°C</p>
          <p class="desc">{{ weather.condition }}</p>
        </div>
      }
    </div>
  `
})
export class LocalWeatherComponent {
  // 1. Get Location Coordinates via Geolocation Web API Promise
  locationResource = resource<Coords | undefined>({
    loader: () => new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(
        (pos) => resolve({ lat: pos.coords.latitude, lng: pos.coords.longitude }),
        (err) => reject(err),
        { timeout: 5000 }
      );
    })
  });

  // 2. Fetch Weather based on derived coordinates
  weatherResource = httpResource<any>(() => {
    const coords = this.locationResource.value();
    if (!coords) return undefined; // Skips fetch if coordinates aren't resolved yet
    return `/api/weather?lat=${coords.lat}&lng=${coords.lng}`;
  });
}

Comparison Cheat Sheet 📊

Featureresource()rxResource()httpResource()
Loader TypeReturns PromiseReturns ObservableReturns URL string / Request options
Best Used ForWeb APIs, native fetch, PromisesRxJS flows, operators, debouncingStandard Angular REST APIs
Supports InterceptorsNo (unless called inside fetch)Yes (when returning HttpClient calls)Yes (fully integrated with HttpClient)
CancellationUses abortSignalAutomatically unsubscribesAborts active request
Dependency ContextStandard Angular@angular/core/rxjs-interop@angular/common/http

Best Practices & Anti-Patterns ⚠️

❌ Don't use Resources for Mutations (POST, PUT, DELETE)

Resources are designed for read-only, side-effect-free data fetching (GET). Do not trigger state writes, database modifications, or form submissions via a resource loader. For mutations, use standard HttpClient calls inside method handlers:

typescript
// ❌ BAD: Attempting to submit a form via resource
submitForm = resource({
  request: () => this.formData(),
  loader: ({ request }) => this.http.post('/api/submit', request).toPromise()
});

// ✅ GOOD: Handle submission with a method
onSubmit() {
  this.http.post('/api/submit', this.formData()).subscribe(() => {
    this.tableResource.reload(); // Refresh table data
  });
}

❌ Don't Mutate the Resource .value() Directly

The value signal exposed by a resource is read-only. If you need to make local edits to fetched data (e.g., editing a profile template before submitting), write the resource value into a writable signal or linkedSignal():

typescript
// Fetch original profile
profileResource = httpResource(() => `/api/profile`);

// Create a local writable signal that updates when resource value resolves
editableProfile = linkedSignal(() => this.profileResource.value());

✅ Clean up Resources on Component Destroy

Angular automatically cleans up resources and unsubscribes/aborts active requests when the declaring component is destroyed. However, if you run a resource inside a global Singleton Service, ensure it is cleaned up manually or designed with long-running requirements in mind.


Conclusion

The Resource API represents another massive stride toward a fully reactive, zoneless, signal-first Angular ecosystem. By packaging data fetching, loading flags, errors, status enums, and request cancellation into simple declarative functions, Angular drastically cuts down boilerplate code.

Here's your quick cheat sheet:

  • httpResource — Your everyday HTTP calls. Start here.
  • rxResource — When you need RxJS operators like debounceTime or custom retry logic.
  • resource — For Promise-based utilities and native Web APIs (Geolocation, Clipboard, etc.).

Once you've mastered fetching data reactively, the natural next step is handling user input reactively too. Check out our Angular Signal Forms: Zero to Hero Guide to complete the picture.

For a deep understanding of how signals track computations under the hood, revisit the foundational Angular Signals Complete Guide. Happy coding! 🚀

🧠 Test Your Knowledge

Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.

PreviousAngular Signal Forms: The In-Depth 'Zero to Hero' Guide

Written by

Shaik Munsif

Read more articles

Found this helpful? Share it with your network!

On this page

0/22
Question 1 of 10Easy
Score: 0/0

What is the primary purpose of the new Resource API in Angular 19+?