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).
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).
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
insiderequest: () => ... return undefined;
if you don't want the action to trigger. When the request returnsundefined
, the loader won't be called.
Step 3 - Define your reducers (how the action affects your state)
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:
- The source of my update action is a signal:
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'sswitchMap
.
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:
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.
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 theUPDATE
action, meaning the API call will only happen after the last update action has waited 2 seconds. (Identified as// Case 1 ...
in the code)
- There is also a
debounceTime
of 3 seconds on theREFRESH
action (which is initially triggered by an error from theUPDATE
action). (Identified as// Case 2 ...
in the code).
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:
If the source of a
resource
changes, it will cancel the currently executing request. (Similar toswitchMap
behavior in RxJs).
With the current structure of the tool, I don't think it's possible to change this behavior.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, soresource
-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!
- 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.
- The tool, as it stands, does not allow for composing your state, similar to how
@ngrx/signal-store
does withwithFeature
.
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!
- 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?
- 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:
- How do you create an event from a signal that represents a state?
How do you block an API request as long as an action’s source is not valid?
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.
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.
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
.
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.
This way, they can be exposed to actions during their creation.
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 theServerStateContext
. 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!