Full demonstration of Angular resource server state management

By using only signal & resource / httpResource / rxResource provided by Angular, we can build an optimized, reliable, scalable, and clean server-state management solution.

What does server-state management mean?

For me, it mainly means being able to know the loading status of a request to the backend.

For example, when fetching a list of users, I want to know if the data is loading, loaded, or if an error occurred.

Similarly, when updating a user, I want to know the loading state of the user update operation.

Thanks to the new resource methods introduced by the Angular team, we can now track the loading status of each action (request) we implement.

First, I will introduce the minimalist solution I developed, explain how to use it, the limitations of what can be done, and finally detail the internal workings of the tool and the challenges I faced.

How to use server-state management with Angular's resource?

  • 🔗 Works with httpResource, resource, and rxResource
  • ⚡ 100% declarative / reactive / type-safe
  • 🔄 Reacts to other resource result statuses
  • 🚀 Optimistic updates easily supported
  • 🚫 No external libraries
  • ❌ Not a single line of RxJS (unless you really want to)
  • ❗ Handles errors
  • 🛠️ Just 2 small helper functions

The example I will guide you through includes:

  • Displaying a list of users (id & name),
  • Allowing name updates,
  • Deleting users,
  • Retrying an update if an error occurred while updating a user’s name.

Here is the demo link, feel free to explore it.

This is a very common situation in web applications, so I hope it will resonate with you.

To use this solution, you will need a few custom helper functions to enjoy a type-safe, declarative, and reactive tool.

Above all, it avoids writing boilerplate code and gives TypeScript a framework to fully leverage its type-safety.

If you haven't already, import the file: signal-server-state.ts

Step 1 - Define the structure of your state & actions

First, you need to describe, from a typing perspective, the structure you want for your state, and list the actions you will apply.

For the example, I create the UsersState type using the generic ServerStateContext function.

UsersState will be used by the functions we will call later and ensures consistent typing across all declarations.

This is essential to help TypeScript not get lost (and that's not a joke).

Create users state type

Step 2 - Declare your state and actions

To create your state, simply use the main function signalServerState, making sure to pass your UsersState and your initial state as type parameters.

Then, define your actions using the action method.

Don't worry, TypeScript will guide you (and complain if something is wrong).

Create a custom signalServerState

As you can see in the example, I use Angular's resource method to fetch the list of users via the GET action.

Make sure to use request: () => ..., which triggers the loading request when signals inside the request function change. Otherwise, it will only work once.

Return undefined inside request: () => ... return undefined; if you don't want the action to trigger. When the request returns undefined, the loader won't be called.

Step 3 - Define your reducers (how the action affects your state)

Define custom reducer per action

Reducers are called every time an action is triggered.

When an action is triggered, your reducer is called twice: once during loading, and once when the request finishes (or fails).

The tool does not trigger reducers when the resource is in Idle status.

In the GET example, I update the users in the state once they are loaded, and I update the GET status in the state.

Here’s another example for the update action:

Demonstration of implementation of UPDATE action

  • The source of my update action is a signal:

Signal server-state demonstration sources

I make sure to return undefined when I don't want to trigger my update action.

  • In the reducer, I optimistically update the list of users and update the resource's status.
  • Still in the reducer, I ensure that future triggers of UPDATE are blocked via isUpdateDisable while it is loading.

resources have a request strategy similar to RxJS's switchMap.

If you launch a second request before the first one finishes, it cancels the first request.

Now, here’s the DELETE action, which is similar to the previous ones and allows removing a user from the list once the request is completed:

Demonstration of implementation of UPDATE action

Lastly, I’ll introduce the REFRESH action, which is a bit special because it triggers internally in the state when the update action returns an error.

Define the source of an action based on an internal event in your state

The server-state system I presented to you already covers a lot of use cases, but what about situations where you want to re-update a user when the UPDATE request has failed?

For this specific but common case, I made available a storeEvents object that exposes signals which are updated every time the corresponding action is triggered and its status changes.

Use storeEvents signal for internal action source

storeEvents.UPDATE()?.status() allows me to know the status of the UPDATE action, and if it failed, I return the updateItem signal source, which contains the last updated user that failed.

I strongly believe that being able to react to the event of another action, by triggering a new action accordingly, is essential for a server-state solution and keeping the code simple and declarative.

Bonus: The tool also exposes store events, which can be handy for reacting to them.

For example, displaying a notification when an update is completed or when an error occurs.

In the end, by using the two functions we discussed and the generic function to create the state type, we get a solution that is, in my opinion:

  • easy to integrate (and hopefully for you now too),
  • scalable (you can add as many actions as you want),
  • declarative (you know the source, the request, and how the result modifies the state just by looking at the action declaration),
  • reliable & optimized (it only triggers when a source changes and when a request result is received).

Is it still possible to use RxJs operators?

I see you coming... you're playing around with this tool, thinking RxJs is over.

Then one day, well, how can I put it — you realize you do need to use RxJs.

Don't worry, dear friend, it's very likely that this situation will happen to you more than once, and I have a solution for you!

Here is an example on StackBlitz, which follows the same pattern but adds a few RxJs operators for more customization.

In the example:

  • There is a debounceTime on the UPDATE action, meaning the API call will only happen after the last update action has waited 2 seconds. (Identified as // Case 1 ... in the code)

Using RxJs operator with signal action source 1

Using RxJs operator with signal action source 2

  • There is also a debounceTime of 3 seconds on the REFRESH action (which is initially triggered by an error from the UPDATE action). (Identified as // Case 2 ... in the code).

Using RxJs operator with inner store signal event

Make sure to activate the checkbox to force API errors on UPDATE to see the mechanism in action.

These patterns allow for a tool that scales very well with needs — starting simple and adapting properly for more advanced requirements.

Additionally, the rxResource function allows returning an Observable instead of a Promise from the loader, enabling the use of various RxJs operators.

However, this solution does not allow you to do everything, as we will see in the limitations section.

Limitations of the tool

Even though the tool is already very useful, there are still some limitations and potential areas for improvement:

  1. If the source of a resource changes, it will cancel the currently executing request. (Similar to switchMap behavior in RxJs).

    With the current structure of the tool, I don't think it's possible to change this behavior.

  2. The tool is not yet well adapted for real-time use cases (websocket, SSE).

    In these scenarios, you don't need to track the loading status of a request, so resource-type actions are not appropriate.

It is possible to improve the behavior with a few modifications.

If you're interested, let me know in the comments!

  1. It is not possible to apply the same action in parallel. For example, if you want to update the names of several users one after another, triggering a new action before the previous one finishes will automatically cancel the previous one.

It is possible to implement this kind of behavior, but it would be more complex to set up — whereas it is much easier with RxJs.

  1. The tool, as it stands, does not allow for composing your state, similar to how @ngrx/signal-store does with withFeature.

This could also be done, but it adds a layer of complexity that I wanted to avoid for this demo.

Let me know if you'd like to know more!

  1. Typing of the value returned by the reducer could be improved. Currently, you can add properties freely, which could lead to confusion.

This behavior can also be corrected. Let me know if you need help with it.

Now, I will dive into the core of the signal-server-state.ts code.

You'll see how I managed to set up this mechanism without using RxJs.

How does this signal-based server-state management tool work?

  1. The first challenge is being able to retrieve the current state in order to modify it with a reducer when an action is triggered.

With RxJs, this is exactly what the scan operator allows you to do: it retrieves the last emitted value and lets you transform it with the new input value.

If you want an example of a similar server-state management system using scan, here’s an article on the topic: My favorite pattern to manage server-state cleanly with RxJs

But that’s not all — using only signals brings two additional challenges that don’t exist with an event-driven pattern handled by RxJs:

  1. How do you create an event from a signal that represents a state?
  2. How do you block an API request as long as an action’s source is not valid?

  3. A fourth challenge is: how to share the internal store events (UPDATE loading, loaded, error) so they can be reused as sources?

Let me walk you through all this.

Retrieve the current state to modify it with a reducer

This is the foundation of any store: allowing the internal state to be modified when an action is applied using its associated reducer.

At first, I considered using a fully declarative pattern to create this tool — but it’s not possible with signals (whereas it is perfectly possible with RxJs).

The solution I found is to create a signal that holds the state inside the signalServerState function.

inner state signal

Now, the next step is figuring out how to update it when an action occurs.

How to create events from a signal to update the state

I created an effect for each action that observes changes in the status of each action’s resource signal.

Create events based on signal using effect

For each status change of a resource signal, I update the state by using the action’s associated reducer.

Thanks to this imperative solution, we can create an event system.

How to block an API request as long as the source of an action is not valid?

Actions like an update are events that should wait until they are triggered to launch an API call.

We just saw how to create events based on the resource of actions, but how can we ensure that the API call for a resource only occurs when the action’s source changes?

I’ve already mentioned the solution: to prevent triggering the API call, you simply have the request function of the resource return undefined.

However, the resource still changes its value, and its status becomes Idle, which triggers the associated effect.

To prevent the reducer from being called, I added a condition that checks the resource status and stops execution if the status is Idle.

Prevent calling the reducer action when request is Idle

This system allows updating our state only when an action is loading, loaded, or has failed.

That was the entire goal of this server-state management approach.

How can an action have another action’s event as its source?

To allow an action to react to events coming from other actions, there’s no choice — you must create internal signals right when the store is created.

Create internal action sources

This way, they can be exposed to actions during their creation.

Share action internal source

Use store source as action source

The limitation regarding typing is that we can't know, at this point, the type returned by the resource (what the API call returns). A solution could be to map each action with its return type directly in the ServerStateContext. But I find it too restrictive and a worse DX.

My thoughts on this

Although I’m presenting it to you here, I haven’t had the chance to test it in production yet.

Some adjustments might be necessary.

I find the tool really practical to already handle a lot of situations, and its declarative style makes it very clear what the code’s intentions are.

The tool is just a small file that contains all this logic, and I think that’s a big strength: even if you’re a beginner, it’s accessible enough to evolve it.

It’s also likely easier to introduce it to a team.

I do feel the Developer Experience (DX) suffers when you need to use RxJs operators, as it hurts readability a bit.

But I found a solution that could be the best of both worlds!

I thought: why not keep exactly the tool I just presented, which is ideal for starting out?

Then, if the needs evolve, we could switch to a very similar tool — still 100% compatible with signals — but this time allowing observables as sources for resources.

It would be a hybrid signal & observable solution, enabling natural RxJs operator usage.

To do that, I’ll need to create a custom resource that returns the same kind of result as the standard ones but accepts an observable as its source.

That would be a huge step forward for scalability.

Feel free to follow me to stay updated when it comes out!

I think the tool itself will remain exactly the same — maybe just with some slight function name changes.

I can't wait to release this hybrid tool so we can use it without limitations!

I'm also working on another server-state management tool that greatly inspired this article and pushes the ideas even further:

Manage CRUD on a list of data with Angular? Here’s the tool I was missing

If you enjoyed the article or have any questions, feel free to leave a comment or follow me on LinkedIn - Romain Geffrault.

Finally, I must admit that I haven’t yet had the opportunity to work with Angular’s new resource methods in production — but that should change very soon, especially with this tool!