DEV Community

Cover image for Simple yet powerful state management in Angular with RxJS

Simple yet powerful state management in Angular with RxJS

Florian Spier on September 04, 2020

TLDR Let’s create our own state management Class with just RxJS/BehaviorSubject (inspired by some well known state management libs). Man...
Collapse
 
alexokrushko profile image
Alex Okrushko

Good article, Florian.
If you add:

  • effect to manage side-effects
  • unsubscribe when this service is destroyed
  • replace BehaviorSubject with ReplaySubject(1) to allow the state to be initialized lazily

then you'd pretty much re-implement @ngrx/component-store :) (ngrx.io/guide/component-store)

Check out the source: github.com/ngrx/platform/blob/mast...

Collapse
 
spierala profile image
Florian Spier

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!

Collapse
 
alexokrushko profile image
Alex Okrushko

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.

Thread Thread
 
spierala profile image
Florian Spier • Edited

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 :)

Collapse
 
jwp profile image
John Peters • Edited

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?

Collapse
 
spierala profile image
Florian Spier • Edited

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.

Collapse
 
maxime1992 profile image
Maxime

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...

Collapse
 
brotherm profile image
MichaelC • Edited

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 in select() (I think).

The workaround is to set it to false once it is used in the subscription like so:

    this.appState.reloadProducts$
      .pipe(
        filter(val => val != null && val === true)
      )
      .subscribe(value => {
        this.loadProducts();
        // Reset state
        this.appState.setReloadProducts(false);
      });
Enter fullscreen mode Exit fullscreen mode
reloadProducts$: Observable<boolean> = this.select((state) => state.reloadProducts);
Enter fullscreen mode Exit fullscreen mode

Do you have another suggestion?

Collapse
 
spierala profile image
Florian Spier

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.

Collapse
 
brotherm profile image
MichaelC

Excellent suggestion! Thank you.

Collapse
 
jwhenry3 profile image
Justin Henry

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.

Collapse
 
spierala profile image
Florian Spier

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 :)

Collapse
 
lallenfrancisl profile image
Allen Francis • Edited

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

protected select<K>(
    mapFn: (state: T) => K,
    changeFn?: (a: K, key: K) => boolean
  ): Observable<K> {
    return this.state$.asObservable().pipe(
      map((state: T) => mapFn(state)),
      distinctUntilChanged(changeFn)
    );
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
spierala profile image
Florian Spier

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.

Collapse
 
killbond profile image
killbond • Edited

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:

city: { 
  id: number, 
  name: string
},
user: {
  id: number,
  name: string,
}
Enter fullscreen mode Exit fullscreen mode

and you want to know when only the value of the city property will change. You wouldn't be able to achieve that, because distinctUntilChanged uses a strict comparsion by default, so the state subject will emit the same value of the city property every time the user changes. Sometimes this can lead to unwanted behaviour, such as requesting data.

Thread Thread
 
spierala profile image
Florian Spier

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 the select 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);

Collapse
 
w0lg0r profile image
Wolfgang Goritschnig • Edited

Really great, thanks a lot - it's such a nice approach that allows to circuit the ngrx-dreadnought in smaller projects :)

Collapse
 
snavarro89 profile image
snavarro89

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

Collapse
 
spierala profile image
Florian Spier • Edited

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 :)

Collapse
 
fmontes profile image
Freddy Montes

Love this approach, implementing it as I type :)

Collapse
 
anthonybrown profile image
Tony Brown

Plus 100 for RxJS
Finally people waking up to the power of reactive programming and streams.

Collapse
 
stefanofavero profile image
Stefano

Hi! How do I delete by name an item from the state and notify to the subscriptions? Thanks

Collapse
 
spierala profile image
Florian Spier • Edited

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).

Collapse
 
stefanofavero profile image
Stefano • Edited

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?

Thread Thread
 
spierala profile image
Florian Spier • Edited

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.

Collapse
 
fireflysemantics profile image
Firefly Semantics Corporation • Edited

Awesome article!! I built a light weight state manager on top of RxJS that you may like.
npmjs.com/package/@fireflysemantic...