Telerik blogs

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:

  • Fetch data from the API
  • Update data locally
  • Asynchronously load local resource
  • It should not be used for mutation such as POST operations.

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:

  • Value
  • Status
  • Error
  • isLoading

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:

Product Resource Statis 2

Once Promise is resolved in the loader function, it prints:

Product Resource Statis 4

As you see, the Status value changed from 2 to 4. The ResourceStatus is defined as below:

  • Idle = 0
  • Error = 1
  • Loading = 2
  • Reloading = 3
  • Resolved = 4
  • Local = 5

In the Angular library, the Resource API works as defined below:

  • It constructs a “Resource” that projects a reactive request to an asynchronous operation defined by a loader function.
  • It exposes the result of the loading operation via signals.
  • The “Resource” is intended for the Read operation, not the operation performing mutation.
  • It cancels any in-progress loads when a new request object becomes available.

The Resource API takes the ResourceOptions type as an input parameter, with the following functions as properties:

  1. Request
  2. Loader
  3. Equal
  4. Injector

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>

Working with the Resource API

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.

Dealing with Multiple Requests

Whenever the productId changes, and if there is an overlap request, the Resource API performs these tasks:

  1. It reloads data by running the loader.
  2. It does not cancel the ongoing request but ignores its result.
  3. It only returns the result of the last request.

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.

Request Is Undefined

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 Request Is Tracked, and the Loader Is Untracked

The Resource API takes two essential parameters:

  1. Request
  2. Loader

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[]>
      );
    },
  });

Summary

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
About the Author

Dhananjay Kumar

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.

Related Posts

Comments

Comments are disabled in preview mode.