Angular resource() and rxResource() APIs: what you need to know
The release of Angular v19, just a few weeks ago, marks a significant milestone in the signal revolution within the framework, with the Input, Model, Output and Signal Queries APIs now officially promoted to stable.
But that's not all! This major version also introduces powerful new tools designed to further advance the signal revolution: the new Resource API.
As the name suggests, this new Resource API is designed to simplify loading asynchronous resources by harnessing the full power of signals!
IMPORTANT: at the time of writing, the new Resource API is still experimental. This means it may change before becoming stable, so use it at your own risk. ?
Let's dive into how it works and how it simplifies handling async resources!
The Resource API
Most signal APIs are synchronous, but in real-world applications, it is essential to handle asynchronous resources, such as fetching data from a server or managing user interactions in real-time.
This is where the new Resource API comes into play.
Using a Resource, you can easily consume an asynchronous resource via signals, allowing you to easily manage data fetching, handle loading states, and trigger a new fetch whenever the associated signal parameters change.
resource( ) function
The easier way to create a Resource is by using the resource() function:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
This function accepts a ResourceOptions configuration object as input, allowing you to specify the following properties:
- request: a reactive function that determines the parameters used to perform the request to the asynchronous resource;
- loader: a loading function that returns a Promise of the resource's value, optionally based on the provided request parameters. This is the only required property of ResourceOptions;
- equal: equality function used to compare the loader's return value;
- injector: overrides the Injector used by the Resource instance to destroy itself when the parent component or service is destroyed.
Thanks to these configurations, we can easily define an asynchronous dependency that will always be consumed efficiently and kept up-to-date.
Resource life cycle
Once a Resource is created, the loader function is executed, then the resulting asynchronous request starts:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
Whenever a signal that the request function depends on changes, the request function runs again, and if it returns new parameters, the loader function is triggered to fetch the updated resource's value:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
If no request function is provided, the loader function will run only once, unless the Resource is reloaded using the reload method (more below).
Finally, once the parent component or service is destroyed, the Resource is also destroyed unless a specific injector has been provided.
In such cases, the Resource will remain active and be destroyed only when the provided injector itself is destroyed.
Aborting requests with abortSignal
To optimize data fetching, a Resource can abort an outstanding requests if the request() computation changes while a previous value is still loading.
To manage this, the loader() function provides an abortSignal, which you can pass to ongoing requests, such as fetch. The request listens for the abortSignal and cancels the operation if it's triggered, ensuring efficient resource management and preventing unnecessary network requests:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
Based on this, it's recommended to use the Resource API primarily for GET requests, as they are typically safe to cancel without causing issues.
For POST or UPDATE requests, canceling might lead to unintended side effects, such as incomplete data submissions or updates. However, if you need similar functionality for these types of requests, you can use the effect() method to safely manage the operations.
How to consume a Resource
The Resource API provides several signal properties for its state, that you can easily use directly within your components or services:
- value: contains the current value of the Resource, or undefined if no value is available. As a WritableSignal, it can be manually updated;
- status: contains the current status of the Resource, indicating what the Resource is doing and what can be expected from its value;
- error: if in the error state, it contains the most recent error raised during the Resource load;
- isLoading: indicates whether the Resource is loading a new value or reloading the existing one.
Here's an example of how to consume a Resource within a component:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
In this example, the Resource is used to fetch data from an API based on the value of the id signal, which can be incremented by clicking a button.
Whenever the user clicks the button, the id signal value changes, triggering the loader function to fetch a new item from the remote API.
The UI automatically updates with the fetched data thanks to the signals properties exposed by the Resource API.
Check the status of a Resource
As mentioned earlier, the status signal provides information about the current state of the resource at any given moment.
The possible values of the status signal are defined by the ResourceStatus enum. Here's a summary of these statuses and their corresponding values:
- Idle = 0: the Resource has no valid request and will not perform any loading. value() is undefined;
- Error = 1: the loading has failed with an error. value() is undefined;
- Loading = 2: the resource is currently loading a new value as a result of a change in its request. value() is undefined;
- Reloading = 3: the resource is currently reloading a fresh value for the same request. value() will continue to return the previously fetched value until the reloading operation completes;
- Resolved = 4: the loading is completed. value() contains the value returned from the loader data-fetching process;
- Local = 5: the value was set locally via set() or update(). value() contains the value manually assigned.
These statuses help track the Resource's progress and facilitate better handling of asynchronous operations in your application.
hasValue( ) function
Given the complexity of these statuses, the Resource API provides a hasValue() method, which returns a boolean based on the current status.
This ensures accurate information about the Resource's status, providing a more reliable way to handle asynchronous operations without relying on the value, which might be undefined in certain states.
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
This method is reactive, allowing you to consume and track it like a signal.
isLoading( ) function
The Resource API also provides an isLoading signal, which returns whether the resource is currently in the Loading or Reloading state:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
Since isLoading is a computed signal, it can be tracked reactively, allowing you to monitor the loading state in real-time using signals APIs.
Resource value as a WritableSignal
The value signal provided by a Resource is a WritableSignal, which allows you to update it manually using the set() and update() functions:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
Note: as you can see, manually updating the value of the signal will also set the status to 5, which means "Local", to indicate that the value was set locally.
The manually set value will persist until either a new value is set or a new request is performed, which will override it with a new value:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request, abortSignal }) => fetch(RESOURCE_URL + request.id, { signal: abortSignal }) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // Triggers a new request, causing the previous fetch to be aborted // Then the loader function to run again generating a new fetch request id.set(2); console.log(myResource.status()); // Prints: 2 (which means "Loading")
Note: the value signal of the Resource API uses the same pattern of the new LinkedSignal API, but does not use it under the hood. ?
Convenience wrappers methods
To simplify the use of the value signal, the Resource API provides convenience wrappers for the set, update, and asReadonly methods.
The asReadonly method is particularly useful as it returns a read-only instance of the value signal, allowing access only for reading and preventing any accidental modifications.
You can use this approach to create services that manage and track changes to resource values by exporting a read-only instance of the value:
import { Component, resource, signal } from '@angular/core'; const BASE_URL = 'https://jsonplaceholder.typicode.com/todos/'; @Component({ selector: 'my-component', template: ` @if (myResource.value()) { {{ myResource.value().title }} } <button (click)="fetchNext()">Fetch next item</button> ` }) export class MyComponent { private id = signal(1); protected myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(BASE_URL + request.id).then((response) => response.json()), }); protected fetchNext(): void { this.id.update((id) => id + 1); } }
This will prevent consumers from modifying the value, reducing the risk of unintended changes, improving consistency in complex data management.
Reload or destroy a Resource
When working with asynchronous resources, you may face scenarios where refreshing the data or destroy the Resource becomes necessary.
To handle these scenarios, the Resource API provides two dedicated methods that offer efficient solutions for managing these actions.
reload( ) function
The reload() method instructs the Resource to re-execute the asynchronous request, ensuring it fetches the most up-to-date data:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
The reload() method returns true if a reload is successfully initiated.
If a reload cannot be performed, either because it is unnecessary, such as when the status is already Loading or Reloading, or unsupported, like when the status is Idle, the method returns false.
destroy( ) function
The destroy() method manually destroys the Resource, destroying any effect() used to track request changes, canceling any pending requests, and setting the status to Idle while resetting the value to undefined:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
After a Resource is destroyed, it will no longer respond to request changes or reload() operations.
Note: at this point, while the value signal remains writable, the Resource will lose its intended purpose and no longer serves its function, becoming useless. ?
rxResource( ) function
Like nearly all signal-based APIs introduced so far, the Resource API also offers an interoperability utility for seamless integration with RxJS.
Instead of using the resource() method to create a Promise-based Resource, you can use the rxResource() method to use Observables:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
Note: the rxResource() method is in fact exposed by the rxjs-interop package.
The Observable produced by the loader() function will consider only the first emitted value, ignoring subsequent emissions.
Thanks for reading so far ?
Thank you all for following me throughout this wonderful 2024. ??
It has been a year full of challenges, but also very rewarding. I have big plans for 2025 and I can't wait to start working on them. ?
I’d like to have your feedback so please leave a comment, like or follow. ?
Then, if you really liked it, share it among your community, tech bros and whoever you want. And don’t forget to follow me on LinkedIn. ??
The above is the detailed content of Angular resource() and rxResource() APIs: what you need to know. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

JavaScript does not require installation because it is already built into modern browsers. You just need a text editor and a browser to get started. 1) In the browser environment, run it by embedding the HTML file through tags. 2) In the Node.js environment, after downloading and installing Node.js, run the JavaScript file through the command line.
