TLDR Let’s create our own state management Class with just RxJS/BehaviorSubject (inspired by some well known state management libs).
Man...
For further actions, you may consider blocking this person and/or reporting abuse
Good article, Florian.
If you add:
effect
to manage side-effectsunsubscribe
when this service is destroyedBehaviorSubject
withReplaySubject(1)
to allow the state to be initialized lazilythen you'd pretty much re-implement @ngrx/component-store :) (ngrx.io/guide/component-store)
Check out the source: github.com/ngrx/platform/blob/mast...
Thanks, that's interesting! ReplaySubject... And I thought every Store uses BehaviorSubject ;)
Still for an DIY StateService I think BehaviorSubject is the most straightforward option.
Regarding unsubscribe:
Maybe I can clarify in the post that the services which extend the
StateService
are supposed to have the lifespan of the application. If such a service would have the lifespan of a component then it should have an option to unsubscribe.effect
is cool!The problem with DIY is that many of the cases are overlooked (and could be error-prone) 😉
What's better than a well-tested tiny lib that handles these for you? 😃
Btw, I typically try to caution about such services that live for the lifespan of the app (unless it's the Globally managed store) - even though I list it as one of the use cases (ngrx.io/guide/component-store/usag... - still working on the docs).
It's very easy to loose track of things.
Is @ngrx/component-store supposed to be used also in long living (app lifespan) singleton services? I thought that state with the lifespan of the app would be more a use case for @ngrx/store.
I will ask you on discord :)
Thanks Florian, I need to read this over again, and again. Reason: I'm not sold on farming off state management as this is only a recent concept with redux etc.
A few Questions if you don't mind
Do you find State Management as its own concern improves our developer lives? Does it make the whole state thing go smoother, faster, easier? How do this tie in with FormControls in Angular?
Hi John. At least my developers life became more fun with state management. I started out with NgRx and was quite happy with it. In NgRx you also work with immutable data and state changes happen explicitly (with dispatching an Action). In the simple StateService in this article we have a similar explicitness with using
setState
inside public API methods. That helps to understand/debug where certain state changes come from.In NgRx you have the principle of Single Source of Truth. It means that there is just one place (Store) which holds the complete application state object. That way you always know where to find/query the state data. The simple StateService has a similar purpose. Services which extend the StateService are also the Single Source of Truth for a specific feature (e.g. the TodosStateService is the Single Source of Truth for everything related to Todos).
With immutable data and Observables it is easily possible to use ChangeDetectionStrategy.OnPush which will improve performance (if you have a lot of components).
Also when working in a Team it is great to have a state management solution in place, just to have a consistent way of updating/reading state that every one can simply follow.
Regarding Form Controls... Ideally the component which holds the form does not know about state management details. The form data could flow into the form component with an
@Input()
and flow out with an@Output()
when submitting the form. But if you use a Facade then the form has no chance to know about state management details anyway.There is one important thing to keep in mind: Template Driven Forms which use two-way-binding with
[(ngModel)]
can mutate the state. So you should use one-way-binding with[ngModel]
or go for Reactive Forms.I'll let OP reply to other questions but to tie some data to an angular form you could give a go to dev.to/maxime1992/building-scalabl...
Nice, simple approach, Florian. I'm trying it out in my current project.
One question, though: I would like to use a Boolean state object to trigger an action in another component when the value is true. Unfortunately, it only works the first time because of the
distinctUntilChanged()
operator inselect()
(I think).The workaround is to set it to
false
once it is used in the subscription like so:Do you have another suggestion?
I think the reload thing is not really a state, therefore I would not make it part of the state interface. It is more like an action. You can easily create an Action with an RxJS Subject and subscribe on it to trigger the API Call.
You can add that Subject to the service which extends the StateService.
Excellent suggestion! Thank you.
I thought it would be pretty cool to build a state management system similar to NGXS/NGRX and Redux using just Rxjs. I essentially used the same concepts but wrapped it in a service and framework agnostic way.
npmjs.com/package/@jwhenry/rx-state
I took from both NGXS and Redux and came up with this idea. It's not as robust as the big boys, but it'll get the job done for small projects.
Hi Justin, nice lib! Yeah I know it is tempting to write your own state management solution with RxJS :) RxJS gives you a great foundation to start off. E.g. with the scan operator and a few more lines of code you almost have a (basic) NgRx Store: How I wrote NgRx Store in 63 lines of code
With RxJS you can easily write the state management of your dreams :)
One addition I think would really be helpful would be the option to specify compare function for the select for using with distinctUntilChanged. Which means the function will be
What is the use-case? Normally you want the Observable returned by
select
to only emit when it has a new Object reference. It's the same in Akita or NgRx. That behaviour encourages also updating state in an immutable manner.Sorry for necroposting, but let's say, we have two different properties in our state. Both are known to be complex, not trivial. To give a better idea, here's an example:
and you want to know when only the value of the
city
property will change. You wouldn't be able to achieve that, becausedistinctUntilChanged
uses a strict comparsion by default, so the state subject will emit the same value of thecity
property every time theuser
changes. Sometimes this can lead to unwanted behaviour, such as requesting data.If you are interested in city changes, then you can write a selector specific for the city:
city$ = this.select(state => state.city);
The
distinctUntilChanges
operator inside theselect
method will make sure that there is only an emission if the city object changes.However, this selector would emit when city or user change:
userAndCityState$ = this.select(state => state);
Really great, thanks a lot - it's such a nice approach that allows to circuit the ngrx-dreadnought in smaller projects :)
Hi Florian
I've been struggling on defining my rest api and how it would interact with my state management in angular app.
Currently I have the following data in my database
Objects:
{
id
data1
data2
task: [
task1: {
array1: [
{
field1
fieldN
}
],
field1,
field2
fieldN
}
task2
task3
taskN
]
anotherArray: []
anotherArray: []
}
Basically my data has embedded objects and arrays, which in turn have nested arrays.
My problem is that I have 10 different views that need either the ObjectList or the single Object. Now this is easy I can just load all the objects with "all" or load only one with "get". But I'm struggling because it just seems unnecessary to return ALL the information as I have views where I only need the "id" and "field1" and not all the arrays with its nested arrays. Each view requires the same list with different fields, and also I have other components that can access and modify only specific objects within the nested arrays (For example I have a component to load the task and update specific fields of the task at once). I know that for this I would have to update the server and then update the store in angular after the success. But having different views that load different fields from the same "selector" methods makes it hard to maintain.
Would you recommend loading all fields at once no matter what view you are loading? Or is there something I can do at the state management to keep the data relevant to the view asking for the state (without loading everything at once from the api)?
Hopefully I was able to explain myself. I already have a working scenario, but the code is all patched up and Im reengineering what I already have working to something that is easier to maintain in the feature while I keep adding fields and/or components that read/update the same record.
Thanks
Yeah! There is more context needed to give a good answer. But this will be quickly off-topic of this blog post.
Maybe you can come to Angular Discord and put your question there?
discord.gg/angular
Feel free to add my discord nickname to your question: @spierala
Regarding State management with the DIY StateService with immutable data: it is recommended to NOT put deeply nested data into the Store/StateService. It becomes just to painful to do the immutable updates for deeply nested objects/arrays. It is better to keep the data structure as flat as possible and setup relations with just IDs.
It is the same challenge for every state management solution which uses immutable data (e.g. NgRx).
Also you have to consider, if really all data has to go to the Store / StateService.
Sometimes it is just one component which needs a specific piece of data. It can be OK to let the component itself fetch the data and forget about the data when the component is destroyed.
It is also important to know how "fresh" the data has to be. If you always need as fresh as possible data then you need to fetch the data again when it is needed by a component.
You can map data to your specific needs e.g. in the function which fetches the data with HttpClient.get and use rxjs/map to transform the data.
Or if the data is stored already in the StateService you could create other more specialized Observables in the Service which extends StateService and use rxjs/map again.
E.g.
todosWithOnlyName$ = this.todos$.pipe(map(todo => ({name: todo.name})))
You see there are a lot of options :)
See you on Discord :)
Love this approach, implementing it as I type :)
Plus 100 for RxJS
Finally people waking up to the power of reactive programming and streams.
Hi! How do I delete by name an item from the state and notify to the subscriptions? Thanks
What do you mean exactly?
Delete an item from an array by a certain criteria? (-> Array.filter will be your friend)
Delete a property from the state object? (you should not do that, but you can set the property to undefined).
When you use setState all subscriptions on the selected state Observables will be notified (if the selected state changed).
I mean: what if I do not need to keep a value stored anymore?
I deleted a stored key/value by storing undefined _ for the _state[key] and then a delete state[key]
Is there any different recommended procedure?
Normally setting to undefined should be enough.
I would not recommend to delete properties. That might create state which is not following the state interface.
delete also mutates the object. But in the StateService we aim for immutable state updates.
Awesome article!! I built a light weight state manager on top of RxJS that you may like.
npmjs.com/package/@fireflysemantic...