Top 5 NgRx Mistakes

Share on facebook
Share on google
Share on twitter
Share on linkedin

NgRx is a popular state management framework implementing the Redux standard for Angular. Nevertheless, it is often one of the hardest things to learn when you are learning Angular. Often the confusion is not in the Redux flow itself (it is quite simple actually) but more in how you actually put it into use.

As a consultant, I often work with new Angular developers that have a background in backend development. They are already experienced developers; they have learned the design patterns, clean code, and architecture but find it hard to apply in the context of Angular.

For big and complex applications, like an online bank, I recommend using NgRx for state management as that is a great way of orchestrating 50+ people working on the same repo.

Why?

  1. Easy traceability of state updates in the Dev Tools
  2. Supports a reactive architecture
  3. Is a well-known pattern in the whole frontend landscape

Nevertheless, there are certain mistakes that limit the usefulness of NgRx, that I tend to see over and over.

This post will show you the most common NgRx mistakes,  so you can avoid making them and enjoy the efficiency, maintenance and scalability benefits you get from NgRx to it’s fullest.

TLDR:

  1. Duplicating store data
  2. Adding properties to the store that can be projected through selectors
  3. Using actions as commands instead of unique events
  4. Mutating the store data
  5. Not decoupling NgRx from the UI layer

Duplicating store data

Commonly, I see developers mistake the store with a denormalized NoSQL database which justifies data duplication in the store. The problem with this is obviously that you now need to maintain updates in multiple places making the app harder to maintain. Also, normally application state is not a distributed system so we don’t need the data duplication eg. for the performance benefits we see with duplicated data in NoSQL databases for distributed systems.

Instead, you want to normalize and flatten the store as in a relational database. For each entity, you create a sub store (often using NgRx entity) and reference other entities using a “foreign key”/id.

The goal here is to create a single source of truth for all our data entities in the store, so you eg. only need to maintain user information in one place in the store.

As a general rule of thumb, you shouldn’t have big arrays for entity types in the store and instead create an Entity map using NgRx entity. Entity maps are simply key-value pairs with unique entities. Having data organized this way makes reading and updating fast and that is what we want to optimize for.

In that way, we can organize the store to be a single source of truth and instead use selectors to project view models for different use cases. That way, when you update a certain entity in the system, you can be sure that the update is reflected in all other places in the application.

Adding properties to the store that can be projected through selectors

I often see beginners to NgRx design the store as their view models (leading to the state duplication we just talked about) and only use selectors to fetch data “as is” from the store.

Not only is this employing a tight coupling between the frontend view models and the store, but it also makes the store less flexible for different use cases and is polluting the store with use case-specific properties, that could otherwise be represented in a selector.

Selectors are cached, so it is no harm to reselect on the same projections (it checks by reference, so you can still experience unnecessary reruns of a selector if you are cloning the selector data eg. filter array operator, see here how to fix this), should you use them in different contexts eg. omit the “hasBeenLoaded property” and instead “calculate” this in a selector based on if the user entity is present in the store.

Really, we want to turn that on its head by having the store normalized and project the view models through the projectors.

Maintaining model variations

For a given entity, we might have three types of models:

  • The DTO from the backend
  • The store entity
  • The view model(s) used to display the store data in a given context

I normally advise on following these conventions for naming the models:

  1. DTOs: postfix with DTO, eg. UserDTO
  2. Store entity: just go with the entity name, eg. User
  3. View modals: Postfix with VM, eg. a user that enriches the User model with icons could be called UserVM

If the models are the same across these three use cases, you might just go with one model per entity.

These conventions make it clear what the responsibility is for the model and makes it easier to find the model you are looking for.

Using actions as commands instead of unique events

There are basically two main ways of naming actions:

  1. As events, reflecting information about what HAS happened, eg. ModalOpened
  2. As commands, reflecting information about what SHOULD happen, eg. OpenModal

I often see developers overuse the commands by:

  • Making multiple action dispatches in the same function
  • Using actions just for setting specific store properties

The problem with this is that you are setting your architecture up for imperative control flow and all the commands are creating a lot of implicit couplings in the application because the action is named after what should happen somewhere else. Normally you don’t want to be tightly coupled with the internal implementation (what should happen).

A more scalable approach for setting state is having actions specified as events because they are decoupled from where they are handled.

I recommend almost always using actions as unique events so they are not coupled to how the action is handled.

The exception where it might make sense to use actions as commands is for action that should trigger ONLY one specific effect (eg. fetchUserInfo) as the implied knowledge about what should happen is a precise description of the action. Some would be more strict with this and say you should always use actions as unique event but I think for this 1 to 1 scenario it makes sense to use it as a command.

Mutating the store data

A core requirement of the Redux flow is that all updates are immutable (return updated clone instead of updating property). Without that, we can not trust the Redux state flow as the state might have been changed in other places.

Also, OnPush change detection requires us to pass a new reference to input for triggering change detection on input changes so mutating the store could cause problems with that.

The easiest way to avoid this from happening is to use the NgRx runtime checks (previously the store freeze meta reducer):

Also, ImmerJS can be a simple way to enforce immutable updates especially if the developers are not too keen about the spread operator.

Not decoupling NgRx from the UI layer

Note: if you are willing to let your application “get married” to NgRx (hard coupling the UI layer to it), you can skip this one.

This point is more of a personal preference, that receives mixed opinions in the Angular community. Given the philosophy that what has worked architecturally on the backend will also work on the frontend and we are developing frontend applications today that are equally (if not more) complex that backend applications, having an equally clear architecture in place should only seem sensible.

By decoupling the NgRx code from the UI layer (the components), we are getting these benefits:

  • Better separation of responsibilities
  • More testable
  • More scalable architecture
  • More reuse of the NgRx boilerplate around actions and selectors
  • Possible to defer state framework decision as you can start with a simple RxJS state management solution (behind a facade) and later change to NgRx without needing to update usages

A counter-argument to this is that the facade takes work to maintain and that some of the NgRx features get “hidden away” so the actions naturally become more “command like” instead of events. I don’t agree with this as you can still do everything even though you go through the facade and the wins we are getting from having a nicely layered architecture are worth it. Otherwise, we wouldn’t have done layered, decoupled architectures in software development in the last couple of decades.

If you want to hear a discussion about the pros and cons of NgRx facades, check out this episode of Adventures in Angular. One of the commonly mentioned cons of using NgRx facades is they can motivate you to:

  1. Using combineLatest instead of reselecting selectors as combineLatest will run selectors immediately and you loose some of the “selector magic” that makes them performant. Always reselect when you want to combine selectors.
  2. Reusing actions/using actions as commands. Even though we are behind a facade, make sure you are using actions as unique events and you are not reusing actions.

If you just avoid these mistakes with facades, you are good to go!

Even though the frontend landscape can seem new and shiny, fundamentally not much has really changed. That is why good developers, that are new to Angular just need to know how their knowledge maps to Angular as the existing concept of good clean software architecture still works regardless of how new and shiny the tool is.

Conclusion

In this post, we saw the five most common NgRx mistakes:

  1. Duplicating store data
  2. Adding properties to the store that can be projected through selectors
  3. Using actions as commands instead of unique events
  4. Mutating the store data
  5. Not decoupling NgRx from the UI layer

All of these are considered mistakes as they violate proven software architecture principles. When you are in doubt and find yourself getting confused with new shiny tools, you just need to stay cool and ask yourself if you are violating the universal software principles.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Hi there!

I’m Christian, a freelance software developer helping people with Angular development. If you like my posts, make sure to follow me on Twitter.

Related Posts and Comments