Resource API 🧪
The Resource API provides automatic loading states, error handling, and reactive updates for async data. Angular offers two types: resource() for Promises and rxResource() for Observables.
⚠️ Angular 20 Changes: The
requestproperty has been renamed toparamsin bothresource()andrxResource(). This guide uses the new Angular 20 API.
🎯 What are Resources?
Section titled “🎯 What are Resources?”Resources automatically manage async data with built-in loading, error, and success states. They integrate seamlessly with Angular signals.
Key Benefits:
- Automatic Loading States - Built-in
isLoading()signal - Error Handling - Built-in
error()signal - Reactive Updates - Refetches when parameters change
- Race Condition Prevention - Cancels previous requests
🔄 resource() - For Promises
Section titled “🔄 resource() - For Promises”Use when working with Promises (async/await, HTTP calls converted to Promises):
@Component({ template: ` @if (booksResource.isLoading()) { <p>Loading...</p> } @if (booksResource.error(); as error) { <p>Error: {{ error.message }}</p> } @if (booksResource.value(); as books) { @for (book of books; track book.id) { <div>{{ book.title }}</div> } } `})export class BookListComponent { private bookService = inject(BookService);
booksResource = resource({ loader: () => this.bookService.getBooks() // Returns Promise<Book[]> });}With Parameters
Section titled “With Parameters”Automatically refetches when parameters change:
@Component({ template: ` <input [value]="searchQuery()" (input)="updateSearch($event)" placeholder="Search..." /> @if (searchResource.value(); as books) { @for (book of books; track book.id) { <div>{{ book.title }}</div> } } `})export class SearchComponent { searchQuery = signal('');
searchResource = resource({ params: () => ({ query: this.searchQuery() }), loader: ({ params }) => this.bookService.search(params.query) });
updateSearch(event: Event) { this.searchQuery.set((event.target as HTMLInputElement).value); }}📡 rxResource() - For Observables
Section titled “📡 rxResource() - For Observables”Use when working with Observables (RxJS streams, HTTP calls):
@Component({ template: ` @if (dataResource.isLoading()) { <p>Loading...</p> } @if (dataResource.value(); as data) { <div>{{ data.message }}</div> } `})export class LiveDataComponent { dataResource = rxResource({ stream: () => { return timer(0, 1000).pipe( switchMap(() => this.dataService.getLiveData()) // Returns Observable ); } });}With Debouncing
Section titled “With Debouncing”Debounce search to avoid too many requests:
@Component({ template: ` <input [value]="searchQuery()" (input)="updateSearch($event)" /> @if (searchResource.value(); as results) { @for (item of results; track item.id) { <div>{{ item.name }}</div> } } `})export class DebouncedSearchComponent { searchQuery = signal('');
searchResource = rxResource({ params: () => ({ query: this.searchQuery() }), stream: ({ params }) => { return of(params.query).pipe( debounceTime(300), // 300ms debounce switchMap((query) => this.searchService.search(query)) ); } });
updateSearch(event: Event) { this.searchQuery.set((event.target as HTMLInputElement).value); }}🔧 Key Differences
Section titled “🔧 Key Differences”| Feature | resource() | rxResource() |
|---|---|---|
| Input | Promise | Observable |
| Property | loader | stream |
| Parameters | params (v20, was request) | params (v20, was request) |
| Use Case | async/await, HTTP → Promise | RxJS streams, HTTP → Observable |
| Cancellation | AbortSignal | Automatic unsubscribe |
| Multiple Values | Single value | Can emit multiple values |
🎯 Manual Loading
Section titled “🎯 Manual Loading”Control when loading happens:
@Component({ template: ` <button (click)="loadData()">Load Data</button> @if (dataResource.value(); as data) { <div>{{ data.message }}</div> } `})export class ManualLoadComponent { shouldLoad = signal<boolean | undefined>(undefined);
dataResource = resource({ params: () => this.shouldLoad() ? {} : undefined, // undefined = don't load loader: () => this.dataService.getData() });
loadData() { this.shouldLoad.set(true); }}✅ Best Practices
Section titled “✅ Best Practices”1. Always handle all states:
@if (resource.isLoading()) { <p>Loading...</p> }@if (resource.error(); as error) { <p>Error: {{ error.message }}</p> }@if (resource.value(); as data) { <div>{{ data }}</div> }2. Provide default values:
booksResource = resource({ loader: () => this.bookService.getBooks(), defaultValue: [] // Prevents undefined});3. Use reload() for manual refresh:
<button (click)="booksResource.reload()">Refresh</button>🎯 Quick Reference
Section titled “🎯 Quick Reference”resource()→ Promises (async/await)rxResource()→ Observables (RxJS)params→ Parameters that trigger refetch (renamed fromrequestin v20)loader→ Function that returns Promise (resource only)stream→ Function that returns Observable (rxResource only)defaultValue→ Initial value (prevents undefined)reload()→ Manual refreshisLoading()→ Loading state signalerror()→ Error state signalvalue()→ Data signalstatus()→ Resource status string (v20: ‘idle’, ‘loading’, ‘reloading’, ‘error’, ‘resolved’, ‘local’)
🚀 Next Steps
Section titled “🚀 Next Steps”- HttpResource API - Specialized HTTP handling
- Control Flow - New template syntax
- Zoneless Angular - Performance optimization
Remember: Resources handle loading states, errors, and race conditions automatically! 🧪