|
| 1 | +--- |
| 2 | +date: 2025-02-09 |
| 3 | +title: Angular resource and rxResource API |
| 4 | +description: Angular v19 introduces a powerful new API for handling asynchronous data the `resource` and `rxResource` APIs. These new primitives integrate seamlessly with Angular's signal-based reactivity model, making it easier to fetch, manage, and update data while keeping track of loading and error states. |
| 5 | +tags: ['angular', 'resource-api', 'signals'] |
| 6 | +category: Angular |
| 7 | +--- |
| 8 | + |
| 9 | +Angular v19 introduces a powerful new API for handling asynchronous data: the `resource` and `rxResource` APIs. These new primitives integrate seamlessly with Angular's signal-based reactivity model, making it easier to fetch, manage, and update data while keeping track of loading and error states. |
| 10 | + |
| 11 | +## Why Use the Resource API? |
| 12 | + |
| 13 | +Before `resource`, developers often relied on `HttpClient` with `Observables` or `Promises` to manage async data. While this approach worked well, it required additional RxJS operators like `switchMap` to handle reactive changes effectively. The new `resource` API simplifies this by: |
| 14 | + |
| 15 | +- Automatically tracking dependent signals and reloading data when they change. |
| 16 | +- Handling request cancellation to prevent race conditions. |
| 17 | +- Providing built-in methods for local updates and manual refreshes. |
| 18 | +- Offering `rxResource` for developers who prefer working with Observables. |
| 19 | + |
| 20 | +## `resource` |
| 21 | + |
| 22 | +```typescript |
| 23 | +import { resource, Signal } from '@angular/core'; |
| 24 | +import { Component, computed, effect, signal } from '@angular/core'; |
| 25 | + |
| 26 | +interface MissionLog { |
| 27 | + id: number; |
| 28 | + title: string; |
| 29 | + description: string; |
| 30 | + status: 'pending' | 'completed'; |
| 31 | +} |
| 32 | + |
| 33 | +@Component({ selector: 'app-mission-log', templateUrl: './mission-log.component.html' }) |
| 34 | +export class MissionLogComponent { |
| 35 | + missionId = signal(1); |
| 36 | + |
| 37 | + missionResource = resource({ |
| 38 | + request: this.missionId, |
| 39 | + loader: ({ request: id }) => fetch(`https://api.spacedata.com/missions/${id}`).then((res) => res.json()), |
| 40 | + }); |
| 41 | + |
| 42 | + loading = this.missionResource.isLoading; |
| 43 | + error = this.missionResource.error; |
| 44 | + |
| 45 | + nextMission() { |
| 46 | + this.missionId.update((id) => id + 1); |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +### Key Takeaways: |
| 52 | + |
| 53 | +- `request`: Tracks the `missionId` signal so the loader fetches new data when it changes. |
| 54 | +- `loader`: Defines how the mission data is fetched. |
| 55 | +- `isLoading` and `error`: Useful for UI updates. |
| 56 | + |
| 57 | +### Understanding Loaders |
| 58 | + |
| 59 | +A loader is an asynchronous function that retrieves data when the `request` signal changes. Loaders receive an object with: |
| 60 | + |
| 61 | +- `request`: The computed value from the `request` function. |
| 62 | +- `previous`: The previous state of the resource, which includes the last known value and status. |
| 63 | +- `abortSignal`: A signal used to cancel previous requests to avoid race conditions. |
| 64 | + |
| 65 | +Example of using `abortSignal` in the loader: |
| 66 | + |
| 67 | +```typescript |
| 68 | +missionResource = resource({ |
| 69 | + request: this.missionId, |
| 70 | + loader: ({ request: id, abortSignal }) => |
| 71 | + fetch(`https://api.spacedata.com/missions/${id}`, { signal: abortSignal }).then((res) => res.json()), |
| 72 | +}); |
| 73 | +``` |
| 74 | + |
| 75 | +This ensures that if a new request is made before the previous one completes, the older request is aborted. |
| 76 | + |
| 77 | +:::caution |
| 78 | +Loaders are untracked, meaning that changes inside a loader function do not trigger a re-execution of the function unless the `request` signal changes. |
| 79 | +::: |
| 80 | + |
| 81 | +### Resource Status |
| 82 | + |
| 83 | +The `resource` API provides several status signals to help track the loading process: |
| 84 | + |
| 85 | +- `Idle` No valid request; loader hasn't run yet. |
| 86 | +- `Loading` Loader is currently running. |
| 87 | +- `Resolved` Loader has successfully completed and returned a value. |
| 88 | +- `Error` An error occurred while fetching data. |
| 89 | +- `Reloading` A new request is in progress while keeping the last successful value. |
| 90 | +- `Local` The resource was updated locally via `.set()` or `.update()`. |
| 91 | + |
| 92 | +### Reloading a Resource |
| 93 | + |
| 94 | +The `reload` method allows you to manually re-fetch data from the loader without changing the `request` signal. This is useful when you want to refresh the data on demand, such as when a user clicks a refresh button. Calling `reload()` will re-trigger the loader while maintaining the last known successful value until the new data is fetched. |
| 95 | + |
| 96 | +```typescript |
| 97 | +refreshMission() { |
| 98 | + this.missionResource.reload(); |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### Updating Mission Data Locally |
| 103 | + |
| 104 | +Sometimes, you need to modify data without fetching from the server again. The `resource` API allows local updates: |
| 105 | + |
| 106 | +```typescript |
| 107 | +completeMission() { |
| 108 | + this.missionResource.update(mission => ({ ...mission, status: 'completed' })); |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +This marks the mission as completed locally, without making another API request. |
| 113 | + |
| 114 | +## `rxResource` |
| 115 | + |
| 116 | +For developers who prefer Observables, `rxResource` provides a reactive approach using RxJS: |
| 117 | + |
| 118 | +```typescript |
| 119 | +import { rxResource } from '@angular/core/rxjs-interop'; |
| 120 | +import { HttpClient } from '@angular/common/http'; |
| 121 | +import { inject, signal } from '@angular/core'; |
| 122 | + |
| 123 | +@Component({ selector: 'app-rx-mission-log', templateUrl: './rx-mission-log.component.html' }) |
| 124 | +export class RxMissionLogComponent { |
| 125 | + http = inject(HttpClient); |
| 126 | + limit = signal(5); |
| 127 | + |
| 128 | + missionsResource = rxResource({ |
| 129 | + request: this.limit, |
| 130 | + loader: (limit) => this.http.get<MissionLog[]>(`https://api.spacedata.com/missions?limit=${limit}`), |
| 131 | + }); |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +This integrates seamlessly with Angular’s existing `HttpClient` and RxJS ecosystem, automatically switching to the latest request when the limit changes. |
| 136 | + |
| 137 | +## Conclusion |
| 138 | + |
| 139 | +The new `resource` and `rxResource` APIs are a game-changer for handling async data in Angular. They provide: |
| 140 | + |
| 141 | +- Simple, declarative syntax for fetching data. |
| 142 | +- Automatic request cancellation. |
| 143 | +- Built-in support for local updates. |
| 144 | +- A smooth transition for developers using RxJS. |
0 commit comments