Sharing NgRx state between Angular modules is peanuts

profile
Tim Deschryver
timdeschryver.dev

The problem link

As you’re building your application you’ll be creating different feature modules with maintainability and a small footprint in mind. This way your modules have a clear boundary and will only be loaded when they are needed.

But there are times where you need some information (data) from another module to build your views (components). This is a question that I’m starting to see popping up a lot lately. In this post I will provide an answer to this question in particular.

NgRx Selectors: the basics link

In an application that uses NgRx the whole application state is stored in the store as an (often deeply nested) object, also known as the state tree. As you bootstrap your application this state tree will start off small and it will grow in size as more and more modules are loaded into the application.

The state tree is the single source of truth of the application. Once data is stored it becomes available within the application. To access the state within the components to build up the views, selectors are used.

A selector is a pure function that takes the state as an argument and returns a slice of the store’s state. Instead of just returning the data stored in the store, a selector can compute derived data based on the available data. This allows you to only store the basics, keeping the state tree as compact as possible. Another important part is that selectors could be composed, that is: a selector can take one or multiple other selectors as its input

format_quote

If you’re familiar with working with databases, a selector can be compared with a query

Because selectors are pure functions they can use an optimization technique called memoization, meaning that it only gets executed when one of its arguments is changed.

See Alex Okrushko’s talk NgRx: Selectors are more powerful than you think for more information and some more advanced concepts.

The solution link

To build up to the solution we’ll be creating a small application, where a whole family can fill in their groceries per family member.

The root state link

In the root module we have our family members and we’ll also use @ngrx/router-store to keep track of the router’s state. The entire root state looks as follows:

Resulting in the following state:

Root selectors link

To start off simple, the first step is to create the selectors to select the family members to create a landing page.

Inside the component, use the created getFamilyMembers selector to retrieve the list of family members from the store. Because this is an observable we have to use the async pipe to get the value of the family member list. Notice that we also use the Angular 6.1 keyvalue pipe. This is only needed because the state is stored in a normalized way and is simply plucked from the state tree. We could have also transformed the objects into an array in the selector, making it so the keyvalue pipe is not necessary.

Resulting in the following home page:

Feature state link

Since this is a grocery application, the feature state will consist of the groceries and a visibility filter to show or hide checked off groceries. We’ll lazily load this module, meaning that the code won’t be loaded during the initial load but will be once the user navigates to the groceries page.

format_quote

This also means the NgRx code won’t be loaded initially. In other words, you can’t select data from the lazy loaded module at this point. Also, the reducers and the effects won’t get invoked when an action is dispatched.

When the feature module is loaded, NgRx will append the feature’s reducers to the current reducers. The state tree now looks like this:

Feature modules link

The groceries state is added to the state tree as groceries because this is how it's defined.

Feature selectors link

Now that we know how the feature state looks, we can write the selectors to select the data.

Notice that we’re using createFeatureSelector to select the state of groceries. Here again we have to pass groceries as a parameter to select the groceries slice from the state tree. Also notice the fromGroceries.selectAll method to select all the groceries, this is a baked in selector that comes with @ngrx/entity.

With this done we can create our first selector that returns derived data. We’ll use the getGroceries selector and return the groceries grouped by family member which makes it easier to lookup the groceries for a specific family member later.

Router selectors link

With our state and groceries selectors in place we can create the grocery page for a family member. This page is accessible via /groceries/:id, e.g. /groceries/mom. To show the groceries of the mom in this case we’ll have to pluck the family member id from the URL, select the family member from the store, and finally select the groceries of the family member from the store.

Time to write the selectors needed to create the grocery page.

Notice that we also filter out the groceries based on if they are checked off or not. We also add an extra checkedOff boolean property to make it easier to work with in our view, in comparison to a nullable date.

With these selectors in place we can use getActiveFamilyMember and getVisibleActiveFamilyMemberGroceries in the component and build the following view:

To give one more example, I’m also going to show you the selectors needed to create a page to show all the groceries of the whole family.

Just like before, the getFamilyMembers selector from the root module is being used and also the base selector getGroceriesByFamilyMember.

We don’t need anything more, so we can create our family view:

Recap link

As you can already see in the examples above sharing data between modules can be peanuts if selectors are used. This comes with the extra benefit that:

Keep in mind that if a feature module is being lazy loaded, that in order to select the data, the module must have been loaded. We can use a custom preloading strategy to load specific lazy loaded modules on the start up.

If you want to see the project, you can take a look at the GitHub repo, or play with it in StackBlitz.

A big thank you to Nate Lapinski, Max Wizard K, Alex Okrushko and Todd Palmer for reviewing this post.

Here are some more resources if you want to know more link

Incoming links

Outgoing links

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post

Twitter LinkedIn