Replies: 2 comments
-
|
Hey @LcsGa, great work! Just to be clear upfront: I can’t speak for the whole team here, this is just my personal opinion. From my perspective, I don’t think we should proceed with this right now - but since you’ve invested so much, let me explain the reasoning behind my position. I think we all agree that server-state synchronization is still far from ideal. At the moment, we only have withEntities, which is focused on client-side state. What’s really missing is proper support for backend interaction - especially mutations. The main reason I’m hesitant is that the Angular framework itself is working on first-class primitives for this. We already see this on the fetching side with resources, and it has been communicated multiple times that mutations will follow. While there’s no final commitment yet, it’s very likely they’ll land soon. In my opinion, SignalStore should build on top of Angular’s primitives, rather than trying to solve the problem independently. That means embracing resources first and waiting for Angular’s mutation API before adding our own logic on top. If we later see that something’s missing or could be improved, we can still extend it - but we should align with Angular’s direction. We already have a PR for TL;DR: If I were you, I’d publish your work as a community library. That’s exactly what I (and some others) did with ngrx-toolkit: it follows a similar idea but explicitly separates between resources and mutations. Again, great job and thanks for bringing it up. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for the suggestion @LcsGa, but the proposal is not flexible enough to become part of the core package. A few examples:
You can find in the official docs the example of The implementation can be extended to support mulitple requestStatus flags within the same store, so the usage would look like this: const TodosStore = signalStore(
withRequestStatus('create'),
withRequestStatus('load'),
);
// usage:
const todosStore = inject(TodosStore);
todosStore.createRequestStatus
todosStore.isCreatePending
todosStore.isCreateFulfilled
todosStore.createError
todosStore.loadRequestStatus
todosStore.isLoadPending
todosStore.isLoadFulfilled
todosStore.loadErrorI'd recommend this more granular approach for better flexibility. If multiple stores need all CRUD operations, another custom feature can be created on top of export function withCrudRequestStatuses() {
return signalStoreFeature(
withRequestStatus('load'),
withRequestStatus('create'),
withRequestStatus('update'),
withRequestStatus('delete')
);
}If the proposed solution works for your application, that's great! SignalStore is designed with flexibility in mind to allow full customization for various use cases. However, if you plan to create a community plugin for this, consider the approach that I suggested for better flexibility. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Which @ngrx/* package(s) are relevant/related to the feature request?
signals
Information
Summary / TL;DR
This proposal introduces a new withServerState feature for ngrx/signals. It aims to provide a granular, flexible, and intuitive way to manage the status of asynchronous operations (e.g., loading, creating, error) for individual slices of state within a SignalStore, rather than relying on a single, top-level request status.
The desired API would feel similar to modern data-fetching patterns, allowing developers to write:
Problem Statement
I've been using SignalStore for several months and have found it incredibly powerful. For managing the status of async requests, I initially implemented a
withRequestStatusfeature based on the official example. However, I quickly realized its limitations in real-world applications.When a store manages multiple entities that can be fetched, created, or updated independently, a single, flat requestStatus property (
{ loading: boolean, error: Error | null }) becomes ambiguous and hard to manage. It's impossible to know which operation is loading or has failed.The current approach forces developers to either create multiple status slices (
productsStatus,xxxStatus, etc.) or manage boolean flags manually, both of which add boilerplate and reduce clarity. There is a clear need for a built-in mechanism to associate a request's lifecycle with the specific piece of state it affects.Proposed Solution
My proposal is to introduce a new feature,
withServerState, built upon existing primitives likewithPropsandsignalState. It treats each targeted state property as a "server-driven" entity, encapsulating both its value and its request status.1. The ServerState Wrapper
First, we define a standard shape for this state, which includes the value and distinct boolean flags for common operations. A
serverState()factory function creates this as a nestedSignalState.2. The withServerState Feature
This is the core of the proposal. It's a
signalStoreFeaturethat takes an initial state object and wraps each of its properties in aserverState.3. State Patching Utilities
To make state updates clean and predictable, a set of helper functions is provided. These return partial
ServerStateobjects, perfect for use withpatchState.Usage Example
Here is how this feature would look in practice inside a
ProductStore.Proof of Concept (POC)
To further illustrate this proposal, I have created a proof-of-concept repository that demonstrates this feature in a concrete example application. I invite everyone to explore the code and see the implementation in a real-world scenario.
You can find the repository here: https://github.com/LcsGa/server-state-feature
Feedback on this implementation is also very welcome.
Bonus: Ergonomic RxJS Operators
To further improve the developer experience and reduce boilerplate within
rxMethod, we can introduce a set of custom RxJStapoperators that automatically patch theServerState.With these operators, the load method becomes incredibly concise:
Open Questions & Points for Discussion
This proposal is a starting point, and I'd like to open the floor to a few specific questions to guide the discussion:
withEntities: How could this concept be extended to work seamlessly withwithEntities? Should the status be per-entity (e.g., tracking the updating state of a specific entity), for the collection as a whole (e.g., when the entire collection is being loaded) or both of them? This could potentially lead to awithServerEntitiesfeature.withServerState,ServerState,... the most appropriate? Can you find any other names?Motivation and Rationale
I have seen other community packages that add TanStack Query-like features to SignalStore. While powerful, they can introduce a high level of complexity and opinionation.
The solution proposed here is intentionally minimalist, flexible, and foundational.
withProps,signalState,patchState), making it easy to understand and adopt.I believe this is a missing piece in the official ngrx/signals package that would solve a very common use case. By providing a basic but powerful primitive like withServerState, we can empower developers to build complex applications more effectively.
What are your thoughts on this? I'm open to any feedback.
Describe any alternatives/workarounds you're currently using
No response
I would be willing to submit a PR to fix this issue
Beta Was this translation helpful? Give feedback.
All reactions