Having fun with State in Angular

Christian Janker
Angular In Depth
Published in
10 min readApr 9, 2019

Conquer the Dark Side of Error, Loading and Routing State

Photo by Jakob Owens on Unsplash

The last couple of weeks there was a little discussion going on about handling loading and error state in NgRx. Two blog posts were written about it shortly and I highly recommend looking into them if you are using NgRx:

Brandon Roberts and
Alex Okrushko

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

I have a different opinion on this topic. I want to take off the NgRx glasses and take a look at the problem from a different point of view. Have fun. Please note that the following is a very opinionated post based on my experience. Feel free to discuss it :)

State management is one of the hardest things to get right. At some point in a career of every software developer, one learns the magic words: It depends.
And it truly does, but: I’m done with “It depends!” 🤭

Sometimes I have the feeling we are missing the point and start discussing things with a very limited view based on one framework and a very specific library.

I think we should not ask what’s the best way to handle loading or error state with NgRx. Independently of a state management library or not, our questions should be more generic. What are some questions that arise: Do we show the errors of our application in one place or do we show them dependent on the component inside of it? Should it be in the store in the first place?

Dan, creator of the redux library, can help us with this decision:

If you’re not sure whether some state is local, ask yourself: “If this component was rendered twice, should this interaction reflect in the other copy?” Whenever the answer is “no”, you found some local state. — Dan Abramov https://overreacted.io/writing-resilient-components/

In my opinion loading- and error states could be more often local state than global state! Even if we show our errors only in one place of our application, it is not shared state. Do I need a single source of truth then? I would even argue that there is already one, it's just not represented in the NgRx state.

In the following example, we are going to look not only at the error state, but we will also try to demystify the marvelous loading state and router state.
Ever had problems with them? I had them for sure 🐼

Let’s get this party started: In our example, we build a simple GitHub commits search view. You can enter the name of your favorite developer and see the last commits she/he has done in public repositories.

Github Commits Search

The Error State 🆘

When we enter a wrong GitHub username we want to display an error message beneath the input field. Just like that:

There are different options to accomplish this:

1️⃣ Managed in the component

The first approach is to handle the error state in the component itself.
We are not using any kind of state management library. This is pretty straight forward. Following template renders our view:

I‘ve tried to write a very declarative template in order to keep the example small and readable. I basically wrapped the directives from Angular Material within custom components. There are pros and cons in this approach but that should not bother us for now.

In the corresponding container <commits-with-service-container> we inject the CommitsService and call the readCommitsByUsername method on every change of the username input. This triggers an HTTP request. We can then pass the response with the commits or failures to our presentational components <commits> and <failure> with the help of a template reference variable.
This may seem like a strange container component for you, and I partly agree, but it does everything a container component should do in my opinion:

It should do only one thing.
It should be reusable.
It should not have a template for itself.
It should not have any styles attached.

Therefore we get a highly reusable, testable and solid component. Further, we can decide whether we like to propagate the failures to a custom failure component or handle it directly within the container via a direct service call to some FailureService. In our example, we delegate the failures to our <failure>component.

The onChange function call within the ngOnChanges lifecycle hook is just a little helper function that checks whether the username has changed, calling the given callback if it has. You can take a look at it here.

2️⃣ Managed in the store

The second approach is to manage the error state within the NgRx store. This is the way we would handle our errors if we decided to keep it in a central store. Therefore we create a global object, which represents our state:

The template of the view stays more or less the same. Only the container components changes:

In the container, we do not handle the error case any longer. We just pass the observables that we get from our store selectors to the template, which handles the subscription for us automatically.

The logic with the error state handling is now placed within the reducer function. There we have to make sure to set and reset the error state correctly:

3️⃣ Action Selectors

The third approach is to listen to the error actions of NgRx in the container component itself. Here we make use of the Actions service from NgRx-Effects, but that's just because it's convenient. We could have written our own redux middleware (metareducer in NgRx) and be good with that.

Michael Hladky has shown this approach already in his example. I just wrapped it in a custom RxJs operatorselectFailures:

In my point of view derived state is the right word to explain this. The selectFailures operator can be compared with a “normal” selector you are all familiar with. Just that it does not select from the state of the store. It selects on actions and derives state from it. There is basically no need to save this in a store and therefore the boilerplate in the reducer is reduced. I coined the term Action Selectors for this kind of selectors 🤓

The Loading State 💬

I think loading state is local state and if not it should be!
In the examples above I left out the loading state on purpose. Most of the time loading state and error state are treated together. That's why they often end up in the store together as well.

Why is loading state a local state?
In our example above the loading state could be handled application wide, e.g with a loading spinner that spans over the whole screen or local to the form with a spinner just above the form.
The best indication for a local state is the desired behavior that we would want if we had decided to display two independent search forms next to each other. Should their loading state block each other, when one is loading commits?

Update: Alex pointed out that I kind of contradict the RemoteData approach in the following section, where I put together the loading and error state in one place. I tried to separate the error part from the loading part only for this writeup. I think both should be treated together. If you decide to keep to loading state local the error state should be local too and vice versa.

If we handle the loading state in the commits container we can again decide in which way we want to handle the loading state. We went with a delegation in our example. In the template we added the loading component and fed it with the loading state from the container:

The container is extended with an action selector selectLoading:

The actiontype is either:
Load Commits
Load Commits Success
or
Load Commits Failure

On Load Commits we map to the loading state true.
On Load Commits Success and Load Commits Success we map to the loading state false.

Please note that it is important to subscribe to the action selectors before you dispatch them in order to be sure not to miss any action event. In the example above it is guaranteed that the subscription in the constructor runs before onChanges triggers and so before theLoadCommits action is dispatched.

Avoiding impossible states

There is another way of dealing with remote data and its state. The idea is coming from the elm language and it was first written down by Kris Jenkins in his writeup: How Elm Slays a UI Antipattern

The key takeaway from it is the common UI antipattern, where the state of an ongoing async operation is modeled like the following:

On a slow network connection, where the loading of the commits lasts a bit longer this can become problematic. Then sometimes applications already show a message, based on the empty commits array, that they didn’t find any commits. This is wrong though because the data is just being fetched. This happens because of the very convenient practice of developers to initialize lists with empty arrays in order to avoid undefined checks. This leads to a state where we are no longer able to differentiate between an empty result and an initial value.

Our GitHub commits search example from above had a similar problem:

Non existent user

There is no user yanx2, but because I set an empty array for the commits in the error case I could no longer differentiate between a user that just had no commits done and a user that does not exist at all.

To solve this problem we can introduce a custom type RemoteData, which is a union type of four types :

This was just my first take on typing RemoteData. You can of course also use classes instead of types or just use the already existing library ngx-remotedata.

The initial state of the commits is notAsked. When we are loading the commits we set it to loading and then either to success or failure. In the template, we then access the RemoteData type.

The RemoteData datatype can be used with a state management library as well. Then you would set the different states within the reducer. You can take a look at this in the example repo.

The Router State ☯️

Router state handling can become cumbersome really quickly in combination with the use of a state management library and effects.

1️⃣ NgRx Effects

I think we should not handle routing concerns within effects. I have done it myself in the past and after some time I realized that it was not my best decision. Suddenly I had effects that listened to URL change events and some who listened to success events of async HTTP calls in order to be able to navigate to a different route.
In a simple application, this may be sufficient but later when the application grows there is a point where it becomes hard to understand which event triggered the next or why some event was triggered at all.

Routing should be done by the view or the container component! If you have the feeling that it is the best to handle your routing concerns within effects you most likely have a problem with a proper separation of container-, view- and presentational components.

In our example the CommitsView is in charge of handling the routing. It listens to the route param from our routecommits/:usernameParam and navigates to it on search.

2️⃣ When to use NgRx Router Store?

The router implementation already keeps track of its own state, so I would argue to try not to duplicate it. If there is already a username parameter in the route then there is no need to duplicate that state in a central store. Access these parameters in your container or view component and pass it via input properties or action payloads if necessary.

The only reason we might want to use NgRx Router Store is when we want to be able to record and replay actions and in this case, we don’t want to lose the navigational router events. That’s all.

Update: Tim Deschryver pointed out that the NgRx-Router-Store is great for accessing routing parameters within selectors. This is a valid approach I forgot to mention.

3️⃣ Listening to Success Events

Of course sometimes, you want to navigate to a different route after a successful async operation. If we don’t want to handle this in an effect we can do this in our container component with the help of action selectors that I showed you in the examples above.

Summary

This was a long journey and it took quite a while to write it down. It is a very opinionated post and I had a hard time to structure it, but I didn’t want to leave out any thoughts I had regarding this topic.

Feel free to discuss it in the comments.

Follow me on Twitter.

Sources to the example.

🌻 🌻 🌻 🌻 🌻 🐰 🐰 🐰 🐰 🐰 🥕 🥕 🥕 🥕 🥕

Have a nice day!

🌻 🌻 🌻 🌻 🌻 🐰 🐰 🐰 🐰 🐰 🥕 🥕 🥕 🥕 🥕

--

--