Simple state management in Angular with only Services and RxJS

Aslan Vatsaev on February 10, 2019

One of the most challenging things in software development is state management. Currently there are several state management libraries for Angula... [Read Full]
 

Definitely my preference. Generally the applications I find myself building don't need anything more than this, and pure observable contexts make things so much more composable, no magic strings anywhere.

 

Exactly what I'm trying to do in my app. An advice: don't put HTTP, async services, etc. into the store. Keep it separate. State management has nothing to do with such services and business logic. Ngrx Effects is a terrible mix of two concepts.

 

You're right, side effects must always be separated from state management, this was a quick example, i'll try to clean it up when I have some free time.

 

This looks really nice. After 4ish projects across React and Angular, I have yet to find the Redux pattern remotely worth it... But I'll say that has a lot to do with particular executions...

Regardless, this seems like this would make life a lot easier. :)

One question, tho: is the shareReplay in completedTodos$ not redundant? It would call shareReplay twice in a row in the pipe.

 

To be honest I'm not sure, I've put it for good measure, but theoretically yes, i didn't have to use shareReplay on filtering considering that the original source is already multicast, I'll do some tests to confirm, and remove it later.

 

This is exactly what I do in my project. I trialled ngrx/store but found it so overly bloated with a ridiculous amount of boilerplate for very little gain. This pattern is simple, elegant, easy to reason about.

 

I notice that the StackBlitz project refers to an undefined trackBy function:

todosTrackFn

I can't see any problem that it is causing. The project compiles ok. TSLint flags the problem in my vscode edit buffer.

 
 

Honestly, unless your app is extremely complicated, anyone using a state management library for Angular has misunderstood the component life cycle and is pretty much trying to make Angular into a React application.

 

Hey Aslan, I do the same thing, except I don't understand the need for sharedReplay. You're already sharing the same observable because it's assigned to a public readonly property. And you're also using a BehaviourSubject which should always return the current value to new subscribers. Also, why not just use a ReplaySubject if you need the previous value? You lose getValue, but it's not such a big deal because you probably want to subscribe anyway. What am I missing?

 

BehaviourSubject is not a simple observable, it's an observer AND an observable at the same time that can give you its latest state synchronously.

You're already sharing the same observable because it's assigned to a public readonly property

That doesn't mean the observer is shared, and that doesn't change the fact that when someone subscribes to todos$, a new dedicated observer will be created for it. A shared observable means that all subscribers are getting their data stream from a single observable source. (lookup rxjs multicasting)

More info here: learnrxjs.io/operators/multicastin...

Also, why not just use a ReplaySubject if you need the previous value? You lose getValue...

I need to be able to get the previous state synchronously in the reducers, as I already explained in my other comment, reducers are synchronous functions that take previous state and reduce it to a new state in an immutable way.

The reason we need the replay part is because some components might be rendered asynchronously (after fetching some data from api for ex), and might miss the emits in todos$ observable, so when they subscribe to it they won't know what happened in the source before component came to life, the replay will emit the latest value to all observers upon subscription.

On the other hand, the second share replay is not necessary, I updated the code.

Hope I answered your questions.

 

I really liked this approach and I'm definitively going to use it! But I wonder how could it be used when we actually have to fetch data from an API?

 
 

Aslan, I like this approach quite a bit, but I have the same questions. What would it look like when fetching data from an API?

Hey @klouddy @gabrielaraujof , I've updated the stackblitz example with a real REST API and some interesting techniques on how to do optimistic updates and rollbacks: stackblitz.com/edit/angular-rxjs-s...

 

I've updated the stackblitz example with a rest api

 

why use behavior subject with sharereplay?
get todos() is also overkill. It's not a good way to get data from observer, except in async pipe. For adding todo we should use scan operator, same as for mark as completed. Subject with shareReplay (or ReplaySubject) solves all the problems and BehaviorSubject is really redundant here.

 
 

I've almost made a career out of reducing the complexity and size of Angular 2+ apps by appropriately using services and RxJs instead of convoluted, home-grown solutions to state management.

My resume:

  • Read the Angular docs past the 2nd page.
 

Shouldn't the getter and setter for todos be private as well?

 

Thanks for this. Have been exploring NgRx and have been feeling "this isn't worth it". Did a google search for "simple Angular state management" and here I am.

 

"this isn't worth it"

depends on the case, when I work on large scale apps with a lot of moving parts, it's very worth it

 

Absolutely brilliant! Always know your actual toolset before buying into new ones!

 

A great example of clean and organized code.

 

What do you think about using Subjects as observables in your app state service, I find the very convenient.

 

How do you organize your stores files on the project structure? Do you keep in a separate folder or within the component itself? Thanks!

 

separate modules for each store, inside the module: reducers, effects, facades.

 

I notice that the StackBlitz project differs from your article somewhat. In particular, you convert observables to promises in several places. Can you explain why you do that? Thanks

 

I convert them to Promises so I can use async/await

 

Awesome post, do you have any ideas to better handle business logic and reactive forms? I find it's not that easy to make them well decoupled.

 

If possible, it would be great if you could update the article to match the StackBlitz code. Thanks

 

I'd like to keep the article as simple and lightweight as possible, and i commented the code on stackblitz, but if anything isn't clear enough feel free to ask

code of conduct - report abuse