httpResource (v20+)

📚 What is httpResource?

httpResource() is the HTTP-specific version of resource():

  • Built-in HTTP: No need to write fetch/HttpClient logic
  • Reactive URL: URL can be signal-based, auto-refetches
  • Type-safe: Generic type parameter for response
  • States: loading, resolved, error states built-in
  • SSR support: Works with server-side rendering

🎯 Interview Questions

  • Q1: What is the difference between httpResource and HttpClient?
  • A: httpResource is declarative and signal-based. It auto-refetches when URL signal changes. HttpClient is imperative, you manually call methods.
  • Q2: How do you handle errors with httpResource?
  • A: Check resource.status() === 'error' and resource.error() for the error details.
  • Q3: Does httpResource need HttpClient setup?
  • A: Yes, you still need provideHttpClient() in app config. httpResource uses HttpClient internally.
  • Q4: Can you pass custom headers with httpResource?
  • A: Yes, use the headers option in httpResource config.

🔧 httpResource Concept Demo

User Lookup (Simulated)

In real httpResource, changing userId signal would auto-fetch!

📋 httpResource API

httpResource<T>({ url })

Basic GET request

httpResource<T>({ url, method: 'POST', body })

POST with body

resource.value()

The response data (Signal)

resource.status()

'idle' | 'loading' | 'resolved' | 'error'

resource.isLoading()

Boolean shortcut for loading state

resource.error()

Error details if failed

resource.reload()

Manually trigger refetch

💻 httpResource Code Examples


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

@Component({...})
export class UserComponent {
  // Reactive parameter
  userId = signal(1);
  
  // ===== Basic httpResource (GET) =====
  
  userResource = httpResource<User>({
    url: () => `/api/users/${this.userId()}`
  });
  
  // In template:
  // @if (userResource.isLoading()) {
  //   <spinner />
  // }
  // @if (userResource.value(); as user) {
  //   {{ user.name }}
  // }
  // @if (userResource.error(); as error) {
  //   {{ error.message }}
  // }
  
  // ===== Static URL =====
  
  allUsers = httpResource<User[]>({
    url: '/api/users'
  });
  
  // ===== With Headers =====
  
  protectedResource = httpResource<Data>({
    url: '/api/protected',
    headers: {
      'Authorization': 'Bearer token123',
      'Accept': 'application/json'
    }
  });
  
  // ===== POST Request =====
  
  createUser = httpResource<User>({
    url: '/api/users',
    method: 'POST',
    body: () => ({
      name: this.newUserName(),
      email: this.newUserEmail()
    })
  });
  
  // ===== With Query Parameters =====
  
  searchTerm = signal('');
  page = signal(1);
  
  searchResults = httpResource<SearchResult[]>({
    url: () => `/api/search?q=${this.searchTerm()}&page=${this.page()}`
  });
  
  // ===== Using Resource State =====
  
  // Computed based on resource
  userCount = computed(() => this.allUsers.value()?.length ?? 0);
  
  isUserLoaded = computed(() => 
    this.userResource.status() === 'resolved'
  );
  
  // ===== Manual Reload =====
  
  refresh() {
    this.userResource.reload();
  }
  
  // ===== Error Handling =====
  
  handleError() {
    const error = this.userResource.error();
    if (error) {
      console.error('Failed to load:', error.message);
      // error.status - HTTP status code
      // error.statusText - Status text
    }
  }
}

// ===== Setup in app.config.ts =====

import { provideHttpClient, withFetch } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()), // Required!
    // Or without fetch: provideHttpClient()
  ]
};

// ===== Comparison: HttpClient vs httpResource =====

// HttpClient (imperative)
@Component({...})
export class OldWay {
  private http = inject(HttpClient);
  
  user = signal<User | null>(null);
  loading = signal(false);
  error = signal<Error | null>(null);
  
  loadUser(id: number) {
    this.loading.set(true);
    this.http.get<User>(`/api/users/${id}`).subscribe({
      next: user => {
        this.user.set(user);
        this.loading.set(false);
      },
      error: err => {
        this.error.set(err);
        this.loading.set(false);
      }
    });
  }
}

// httpResource (declarative)
@Component({...})
export class NewWay {
  userId = signal(1);
  
  // Everything handled automatically!
  user = httpResource<User>({
    url: () => `/api/users/${this.userId()}`
  });
  
  // Change userId and it auto-refetches
  changeUser(id: number) {
    this.userId.set(id); // That's it!
  }
}