Get a head-start on the Resource API, coming very soon with Angular 19!
Angular 19 will introduce a new reactive API called Resource API. The primary purpose of the Resource API is to load the resources, such as:
It should mainly be used to load data from the API using the HTTP protocol. You can create a straightforward resource as shown below:
export class AppComponent {
productResource = resource({
loader: async () => {
return Promise.resolve({ name: 'Book', price: 100 });
}
})
}
By default, the loader function returns the promise, and you can read the value on the template below:
<h2>{{productResource.value()?.name}}</h2>
The Resource API returns a WritableResource and has read-only properties such as:
All of them are signal types, which means you can track them inside an effect.
constructor(){
effect(()=>{
console.log('Product Resource Data ', this.productResource.value());
console.log('Product Resource Staus ', this.productResource.status());
console.log('Product Resource Error ', this.productResource.error());
console.log('Product Resource is Loading ', this.productResource.isLoading());
})
}
Angular prints values as below:
Once Promise
is resolved in the loader function, it prints:
As you see, the Status value changed from 2 to 4. The ResourceStatus is defined as below:
In the Angular library, the Resource API works as defined below:
The Resource API takes the ResourceOptions type as an input parameter, with the following functions as properties:
The request function determines the request to be made. Whenever the request changes, the loader will be triggered to fetch a new value and cancel any ongoing loading operation.
Let’s understand the request with an example. You want to fetch new product whenever a signal value changes:
id: WritableSignal<number> = signal(1);
fetchnewProduct() {
this.id.update(a => a + 1);
}
To fetch a new product each time the value of the id signal is updated, use it in the Resource API as below:
productResource = resource({
request: this.id,
loader: async () => {
let c = this.id();
return Promise.resolve({ name: 'Book', price: 100 * c });
}
})
As the Resource API returns the WritableResource
, Angular allows the local update of the value of the Resource API.
updateResource(): void {
this.productResource.update((value: any) => {
if (value.price == 500) {
console.log('value if:', value);
return { ...value, price: 1 };
}
console.log('value', value);
return { ...value };
})
}
A button can trigger the function by clicking on the template.
<button (click)="updateResource()">Update Resource Locally</button>
The primary purpose of the Resource API is to load data. To fetch this data, let’s create an interface for the Product.
export interface Product {
id: number,
name: string,
price: number
}
To make an API call, create a resource, and use fetch within the loader to perform the GET operation:
products?: Product[] = [];
productApiResource = resource({
loader: async () => {
return fetch('http://localhost:3000/products').then(
(res) => res.json() as Promise<Product[]>
);
},
});
You can use the productApiResource
on the template to bind data to a table.
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
@for(p of productApiResource.value(); track p.id){
<tr>
<td>{{p.id}}</td>
<td>{{p.name}}</td>
<td>{{p.price}}</td>
</tr>
}
</tbody>
</table>
You can fetch a specific product by creating a signal for productId
. The loader will execute whenever productId
is updated or set with the new value.
productId: WritableSignal<number> = signal(1);
productApiResource = resource({
request: () => ({ id: this.productId() }),
loader: async (param) => {
const { id } = param.request as { id: number };
let url = `http://localhost:3000/products?id=${id}`;
return fetch(url).then((res) => res.json() as Promise<Product[]>);
},
});
When the productId
changes, the Resource API automatically fetches the data.
Whenever the productId
changes, and if there is an overlap request, the Resource API performs these tasks:
So, to cancel the previous, ongoing request when the productId
changes, we have to use the abortSignal
of the Resource API.
productId: WritableSignal<number> = signal(1);
productApiResource = resource({
request: () => ({ id: this.productId() }),
loader: async (param) => {
const { id } = param.request as { id: number };
let url = `http://localhost:3000/products?id=${id}`;
return fetch(url, { signal: param.abortSignal }).then((res) => res.json() as Promise<Product[]>);
},
});
Here, the abortSignal
is a JavaScript API supported by all browsers and used by the Fetch API to cancel the ongoing request. So, refrain from confusing abortSignal
with something related to Angular signals.
The loader doesn’t make the API call if the request returns undefined
. So, to postpone the API request in the Resource API loader function, return undefined
from the request.
productApiResource = resource({
request: () => (undefined),
loader: async (param) => {
const { id } = param.request as { id: number };
let url = `http://localhost:3000/products?id=${id}`;
return fetch(url, { signal: param.abortSignal }).then((res) => res.json() as Promise<Product[]>);
},
});
The above loader will never make any API call.
The Resource API takes two essential parameters:
The request parameter is tracked, but the loader is not tracked.
Angular does not execute the loader again if the signal used inside the loader is changed. However, since the request is tracked, a change in the signal used inside the request will allow the loader to be executed again.
The loader function doesn’t automatically rerun when a signal inside the loader changes. Simply updating a signal within the loader won’t trigger the loader to execute again.
However, Angular tracks the signals used inside the request. So, if a signal changes in the request, Angular tracks this change and reruns the loader to fetch the updated data. This approach helps ensure that the loader only reruns when necessary, optimizing performance by avoiding unnecessary API calls.
So, the loader is untracked, and a request is tracked. For example, when the productid
signal changes, the loader won’t automatically rerun so that you won’t receive updated data.
productApiResource = resource({
loader: async (param) => {
let id = this.productId();
let url = `http://localhost:3000/products?id=${id}`;
return fetch(url, { signal: param.abortSignal }).then(
(res) => res.json() as Promise<Product[]>
);
},
});
As of the time of this writing, Angular 19 has not yet been released, so the Resource API may still undergo changes. The Resource API simplifies data loading in a signal-based approach and should be used specifically for data loading, not for mutations.
I hope you find this article helpful. Thank you for reading!
Dhananjay Kumar is an independent trainer and consultant from India. He is a published author, a well-known speaker, a Google Developer Expert, and a 10-time winner of the Microsoft MVP Award. He is the founder of geek97, which trains developers on various technologies so that they can be job-ready, and organizes India's largest Angular Conference, ng-India. He is the author of the best-selling book on Angular, Angular Essential. Find him on Twitter or GitHub.