Introduction
It's been a while since my last article, but I couldn't pass up the opportunity to discuss this exciting new feature Angular is experimenting with.
Angular continuously introduces new features aimed at simplifying developer workflows and enhancing performance. One of its most promising experimental additions is the Resource API, designed specifically to streamline asynchronous data fetching with built-in reactivity. In this article, we'll break down this experimental feature and explore how it could fit into Angular applications.
But first, let's quickly understand why Angular is exploring this new approach.
📖 Why a New Resource API?
Currently, Angular developers rely heavily on Observables and the HttpClient
for asynchronous operations. While powerful, Observables can introduce complexity, particularly in scenarios involving reactive updates, precise error handling, or efficient streaming.
The experimental Resource API aims to address these challenges by:
- Providing a straightforward and intuitive API.
- Facilitating reactive data fetching.
- Including built-in status tracking (loading, error, success).
- Improving performance through finer-grained reactivity.
It's essential to emphasize that this API is experimental and evolving. Certain key features like debouncing and mutations aren't fully implemented yet, as the initial focus is purely on data fetching.
Let's explore what's currently available.
🎯 Core Resource Interface
At the core of this API is the Resource
interface, encapsulating reactive data fetching:
interface Resource<T> {
readonly value: Signal<T>;
readonly status: Signal<ResourceStatus>;
readonly error: Signal<Error | undefined>;
readonly isLoading: Signal<boolean>;
hasValue(): boolean;
}
Key points:
- value: Holds reactive data as a Signal.
- status: Indicates current state (Idle, Loading, Resolved, Error).
- error: Captures any errors during fetch operations.
- isLoading: Easy-to-use loading indicator.
This setup greatly simplifies handling async data in Angular templates.
🛠️ Creating Resources
Angular provides a straightforward resource
function:
const userResource = resource({
request: () => userId(),
loader: async ({ value }) => fetchUser(value),
defaultValue: null,
});
- request: Reactive input for fetching.
- loader: Async function performing the fetch.
- defaultValue: Initial placeholder value before the fetch completes.
You can easily track resource states in templates:
@if (userResource.isLoading()) {
Loading...
} @else if (userResource.hasValue()){
{{ userResource.value().name }}
} @else if (userResource.status() === ResourceStatus.Error) {
Error: {{ userResource.error().message }}
}
🚀 Specialized HTTP Resources
Angular simplifies HTTP fetching further with httpResource
:
const products = httpResource('/api/products');
It integrates directly with Angular's existing HttpClient
, supporting reactive patterns and automatically parsing JSON responses by default.
You can customize your requests:
const productDetail = httpResource({
url: `/api/products/${productId()}`,
method: 'GET',
headers: { Authorization: 'Bearer token' },
});
Additional Response Types
httpResource
also supports different response formats:
-
ArrayBuffer:
httpResource.arrayBuffer()
-
Blob:
httpResource.blob()
-
Text:
httpResource.text()
Example of fetching binary data:
const fileData = httpResource.arrayBuffer('/file.bin');
🎛️ Advanced Features
Type Safety with Runtime Validation
For enhanced type safety, Angular’s Resource API integrates smoothly with runtime validation libraries like Zod:
const ProductSchema = zod.object({
id: zod.number(),
name: zod.string(),
});
const product = httpResource('/api/product', { parse: ProductSchema.parse });
Resource Streaming
Resources can handle streaming responses:
const streamResource = resource({
stream: async ({ value }) => fetchStreamedData(value),
});
RxJS Integration
Existing Observables can easily integrate with rxResource
:
const observableResource = rxResource({
stream: param => observableService.getData(param),
});
🌟 Status and Error Handling
Resources clearly distinguish between different loading states (initial load vs. reload):
enum ResourceStatus {
Idle,
Loading,
Reloading,
Resolved,
Error,
Local,
}
Explicit state tracking simplifies error handling in templates:
@let resourceStatus = resource.status();
@let error = resource.error();
@if (resourceStatus === ResourceStatus.Loading) {
Loading...
} @else if (resourceStatus === ResourceStatus.Resolved) {
Data Loaded
} @else if (resourceStatus === ResourceStatus.Error) {
Error: {{ error.message }}
}
⚙️ Prefetching and Deferred Loading
Resources seamlessly integrate with deferred loading (@defer
blocks), optimizing application performance:
<button #loadBtn>Load Data</button>
@defer (on interaction(loadBtn)) {
<data-cmp [data]="resource.value()"></data-cmp>
}
Prefetching further improves performance:
@defer (prefetch on viewport(elementRef)) {
<component-cmp />
}
📌 Migration and Limitations
This API is experimental, so please consider the following:
- Observables integrate smoothly.
- Structural directives remain compatible.
- Essential features like mutations and debouncing aren't yet implemented.
Exercise caution when adopting experimental APIs, especially in production environments.
if you want to participate in the discussion or contribute to the development of this feature, you can join the Angular Resource API RFC. i alredy learn a lot from the discussion and i hope you can learn too.
🚦 Conclusion
Angular’s experimental Resource API presents an exciting new direction for async data management, addressing existing complexity and enhancing performance. While still evolving, it's definitely worth keeping an eye on.
Angular remains committed to improving the developer experience—making it easier to build better, faster applications. 🚀✨
Want to discuss this further or share your thoughts? Connect with me on Twitter, Threads, LinkedIn, or BlueSky. Let’s explore this together! 💻☕️
If you found this guide helpful, feel free to buy me a coffee. Your support means a lot! ☕️🙏
Top comments (1)
Have you found any good/cool uses for @defer/prefetch in your apps? :) In our case most of our resources live on either a different route, or a dialog such as a few search components, so I have'nt explored it much except for obvious cases.
Btw we also use the parse function for type validation, but we wrap in a way which skips that check in prod. This way we get good errors if things change when developing, without the perf penalties :)
I still believe shared types, or a shared schema file for type gen, is better...but in our case & I assume many others that's simply not possible so validation through parse is the next best thing xD