Derek Zeng

Angular, Redux, ngrx and complexity management

Having a proper way to manage complexity is crucially important in software development.
It leads an established way of adding featurs and fixing bugs, so the software can grow as
big as you wish while still maintaining the basic simple structure that is easy to reason about.
This also ensures that software can be used reliably for a long period of time.

In the past few weeks, while I am working on my reader project based on Angular, this complexity
management question bites again and again. I have to refactor the software several times, each time trying
to come up with a better solution.

Basically the problems I'm trying to solve boils down to the followings:

The million dollar question I have with the diagram above is What's the best way for reader service to interact
with the router? This can also extend to how does different services interact with each other. There may be others
like websocket service, email service etc.

In the reader service, I keep the application state in it. The app state includes the
list of feed/entry objects being displayed.

In the router service, I keep the route related state in it. This includes currently
displayed feed/entry.

I want the keep these state in respective service object and not to duplicate them in any way.

In the original way I tried, all the communication/processing are done in the components because
it's components who have references to those service singleton, not the service themselves.

One example of such interaction is that when the router navigating to a feed, I have to first take the
feed id from the url param then pass it to reader service to fetch the entries for the feed.

In this example, I also have to make sure the getFeed method returns the same object reference from
previous getFeeds call, so I don't have to maintain multiple copy of the same object that may cause inconsistencies.
To implement this, I have to create cache objects in reader service and manage the cache whenever the objects update.
Obviously this is messy.

In addition to that, the component is also responsible for passing data passing through inputs/outputs.
Allowing data flows up and down makes me feel so wary and out of control. I would like the data flow in
a predictable way, like it in redux. But as I described in my previous article and in my opinion, redux is not needed in Angular.

Is it really so?

In the articleManaging states in Angular Applications, Victor
points to a popular Angular library called ngrx. I wasn't so sure if I should use it at first. But after
I had watched this video, I'm convinced it could make my life much easier.

So the idea now is keeping only one centralized state of everything needed to render the application in browser. The state is actually
an observable object. My components now do not need @Input. All it needs is to subscribe to the state observable object and update UI
reactively. The subscription is selective like this:

// in the redux filelet state = { counter: 1}// in the component.....template: '{{counter|async}}'.....this.counter = store.select('counter');

The @Output is not needed too. We use only redux actions. So when a button is clicked, we don't emit Angular event. We dispatch an action
to the global store it then handles the action and make change to the store and the change is propagated throughout the application.

One immediate benefits is that we can start using OnPush change detection strategy in all such components. This is because everything
is dependent on the global states now. (Of course, if we decide to still keep some UI related state to the component, then we can't use
OnPush)

This is very easy to understand.

Okay, to answer my million dollar question, What's the best way for reader service to interact
with the router?

We duplicate router state in the global state and keep them in sync. In fact, router has one way data flow. We click some link and
navigate to the route with some new state and trigger the route action with those state in the component.

If we have another service to push data to the app, we can do the same and merge that data into the global state.

ngrx is based on redux, but it provides an alternative way to implement async actions. In redux, async actions can be implemented using
thunks while in ngrx they are implemented using Effects.

I personally like the Effects very much. Effects separate the concerns of client and server updates.
It creates listeners to intersting actions and update other services that if success do not need
to update the state again. or so called, optimistic update. This allows very responsive UI.
Also since the Effects (like side effects) is separate entities, they are easily testable.

Since I keep a global state now, I free the reader service from keeping internal cache/states, so as to other services.
So the services can become stateless. Also my components become very light now, all they are doing is firing actions.