Streamlining Data Fetching in Angular 19 with resource and rxResource
- Authors
- Name
- Muhammad Ahsan Ayaz
- @codewith_ahsan
- Posted on
- Posted on
Streamlining Data Fetching in Angular 19 with resource and rxResource
Angular 19 introduces some exciting new features for handling asynchronous operations and simplifying data fetching. In this article, we'll explore the experimental resource
and rxResource
methods, which offer powerful alternatives to traditional approaches like computed
, effect
for triggering API calls. And handling data, progress of API calls, and errors in separate variables.
The Problem with Traditional Approaches
Previously, managing asynchronous data fetching and updates in Angular often involved complex combinations of signals, effects, and manual state management. Let's consider a scenario where we fetch user posts and their comments from an API. Using signals and effects, our component code might look something like this:
selectedUser = signal<User | null>(null)
selectedPost = signal<Post | null>(null)
posts = signal<Post[]>([])
loadingPosts = signal<boolean>(false)
postsError = signal<Error | null>()
comments = signal<Comment[]>([])
loadingComments = signal<boolean>(false)
commentsError = signal<Error | null>()
getUserPosts = effect(async () => {
const user = this.selectedUser()
this.postsError.set(null)
this.loadingPosts.set(true)
try {
const posts = await firstValueFrom(this.postService.getPosts(user.id))
this.posts.set(posts)
} catch (err) {
this.postsError(err)
} finally {
this.loadingPosts.set(false)
}
})
getPostComments = effect(async () => {
const post = this.selectedPost()
if (!post) {
return
}
this.commentsError.set(null)
this.loadingComments.set(true)
try {
const comments = await firstValueFrom(this.postService.getComments(post.id))
this.comments.set(comments)
} catch (err) {
this.commentsError.set(err)
} finally {
this.loadingComments.set(false)
}
})
As you can see, this approach requires managing multiple signals for loading states, error states, and selected data, along with asynchronous effects for fetching posts and comments. There's a potential for errors, such as forgetting to reset the selected post or handling error cases gracefully.
resource
Enter Angular 19's resource
method streamlines this process significantly. It simplifies fetching data with automatic handling of loading and error states. Let's refactor the getUserPosts
functionality using the resource
method from @angular/core
:
userPosts = resource<Post[], User>({
request: () => this.selectedUser(),
loader: ({ request: user }) => {
return fetch(`https://jsonplaceholder.typicode.com/users/${user.id}/posts`).then((res) =>
res.json()
)
},
})
The resource
function returns an object of type ResourceRef<T>
which extends a Resource<T>
eventually. In simpler terms, a Resource is an asynchronous depdendency.
For example, an API call. However, instead of promises or observables, a a resource is delivered through signals.
A Resource<T>
has the following signals available:
/**
* The current value of the `Resource`, or `undefined` if there is no current value.
*/
readonly value: Signal<T | undefined>;
/**
* The current status of the `Resource`, which describes what the resource is currently doing and
* what can be expected of its `value`.
*/
readonly status: Signal<ResourceStatus>;
/**
* When in the `error` state, this returns the last known error from the `Resource`.
*/
readonly error: Signal<unknown>;
/**
* Whether this resource is loading a new value (or reloading the existing one).
*/
readonly isLoading: Signal<boolean>;
At the time of writing this article,
resource
andrxResource
methods are still in the experimental stage
With resource
, we define a request function that requires a source signal (selectedUser), and a loader function that performs the actual data fetching using fetch. resource
automatically creates derived signals for isLoading
, error
, value
, and others, which we can use directly in our template:
@if(userPosts.isLoading()) {
<div>Loading...</div>
} @else if (userPosts.error()) {
<div>Error: {{ userPosts.error() }}</div>
} @else {
<app-todo-list [posts]="userPosts.value()"></app-todo-list>
}
This eliminates the need for manual management of loading and error states, making the code much cleaner and more readable.
rxResource
Working with Observables: If you prefer working with RxJS Observables, Angular 19 provides the rxResource
method from the @angular/core/rxjs-interop
package. It's similar to resource
, but the loader function returns an Observable
. Here's how you can rewrite the userPosts
resource using rxResource
and your existing PostService
which already uses observables:
userPosts = rxResource<Post[], User>({
request: () => this.selectedUser(),
loader: ({ request: user }) => this.postService.getPosts(user.id),
})
Notice how loader now directly returns the Observable returned by this.postService.getPosts
. The same derived signals (isLoading, error, value) are available with rxResource
as well.
Error Handling and Conditional Fetching
Both resource
and rxResource
provide a simple mechanism to handle errors. The error
signal contains the error object if the fetch operation fails. You can conditionally display an error message in your template using it as shown in the template example above.
You can also handle cases where you might not always need to make a request. For instance, if there's no selected post, we shouldn't attempt to fetch its comments. You can add a conditional check within the loader function:
postComments = rxResource<Comment[], Post | null>({
request: () => this.selectedPost(),
loader: ({ request: post }) => {
if (!post) {
return of([]) // Return an empty array if no post is selected
}
return this.postService.getComments(post.id)
},
})
Conclusion
Angular 19's resource
and rxResource
methods significantly simplify asynchronous operations and data fetching by abstracting away boilerplate code for loading and error handling. While still experimental, these functions demonstrate the direction Angular is heading towards cleaner and more declarative code. As they become stable, they promise to become a cornerstone for managing data interactions in Angular applications.