Architecting the Store in NGRX

A step-by-step guide to building and architecting the Store in NGRX

This is the second article of a series that aims to explain in detail a step-by-step approach to building an Angular application with NGRX.

In the first article of this series, I wrote a small overview of all the concepts surrounding the NGRX platform.

If you have never worked with NGRX, or have never done something in-depth with, I’d really recommend you read it.

NGRX 8

In the previous article, the concepts were explained using the current NGRX version. In order to keep the articles up to date, from now on I will introduce and explain the same concepts using the latest features released in NGRX version 8. There’s some really cool stuff out!

In particular, we will look at how to create:

actions with createAction

reducers with createReducer

effects with createEffect (in the next article)

What is this article about?

In this article instead, we will explore the process of building the entities that make up our store and will be setting up the entity adapter, the actions, and the reducers for each entity.

As a follow up to one of my previous articles about creating a scalable folders structure, we will see an example of creating store modules as service modules imported by our domain module.

We will start building an application that retrieves live crypto prices from Coincap** and displays them in a customizable dashboard.

** I explored various websites for fetching live prices and Coincap was by far the easiest and clearer provider. Kudos to the team!

Setting up Angular and NGRX

Let’s see how to set up an Angular application and NGRX.

Angular CLI Workspace

The first thing you may want to do is to create a new application with Angular CLI and add the routing and style parameters.

ng new <app> --routing --style=scss

NGRX

Let’s install all the libraries needed to work with NGRX:

npm i @ngrx/store @ngrx/effects @ngrx/entity

And you’re pretty much all set!

At the end of this series, I will be publishing the whole application on Github, so you’ll be able to run, or copy-paste code from there using your own project.

Project’s Folder Structure

Let’s take a brief look at the project structure I opted for:

What’s in store ?

Every folder in store is an Angular Service Module that simply sets up the NGRX store and effects for the Dashboard Module, which is a domain module where our application’s smart components are placed.

Let’s take a look at the DashboardStoreModule which is still very simple:

@NgModule({

imports: [

StoreModule.forFeature('dashboard', dashboardReducer),

// will import effects

],

providers: [

// will import providers

]

})

export class DashboardStoreModule {}

The DashboardModule will then import DashboardStoreModule and the other store modules:

@NgModule({

declarations: [

// components

],

imports: [

// store service modules

DashboardStoreModule,

PricesStoreModule,

AssetsStoreModule, // other modules ],

exports: [RouterModule]

})

export class DashboardModule {}

Where is DashboardModule imported?

The DashboardModule is a lazy-loaded module, so we do not import it from anywhere in our application, but instead, we reference it in our routing module configuration.

In order to make lazy-loaded feature modules work with NGRX, we need to call the forRoot method, although with empty values, for both the StoreModule and the EffectsModule .

@NgModule({

declarations: [AppComponent],

imports: [

// other modules,

StoreModule.forRoot({}, { metaReducers }),

EffectsModule.forRoot([]),

],

bootstrap: [AppComponent]

})

export class AppModule {}

Store Entities

In order to architect the store, we need to first analyze our data structures.

As I mentioned above, the application will feature a dashboard with tiles, and each widget will contain cryptocurrency price tickers. In order to retrieve to display the prices, we first need to load the assets (cryptocurrencies).

We then have 4 different entities that we will use to build our fairly simple store:

a dashboard that contains tiles (or widgets)

a list of assets (cryptocurrencies)

a price (for each asset subscribed)

Flat vs Nested Store

We have two ways of building the store:

a nested structure, by directly adding prices to the assets store

a flat structure, where assets and prices are separated into two separate objects and are only related based on the asset ID

I personally prefer a flat structure.

Why? I have, mistakenly, opted for nested structures in the past and I found the following issues:

by adding a price directly to an asset, we’d be changing the original interface of the entity

deeper, nested structures are more difficult to query

In this simple example, it doesn’t really affect greatly performance or complexity. However, if you plan on building a big application with a complex state, you will quickly see how the selectors and the store complexity creeping up as a result of a nested structure.

My advice is to keep the store as a flat structure of objects and keep the relations between them using unique values.

Dashboard

For simplicity, we will keep the dashboard fairly minimal. We only need two things from a widget:

a tile ID

an asset ID

In order to build this part of the store, we will use @ngrx/entity .

Tile

Let’s first create a class named Tile that represents the model of our state:

export class Tile {

public readonly id = uuid(); constructor(public assetId?: string) {}

}

Of course, unless a tile is preloaded with an asset ID, the asset ID won’t be defined until the user decides which asset to display, which is why we mark as possibly undefined .

Dashboard Adapter

We move on and proceed to create the adapter for our state. Our state will simply be an entity state with a collection of tiles:

Dashboard Actions

In order to create our actions, we will be using the new factory provided by NGRX 8 called createAction .

To summarise the code, we have created 3 actions:

addTile whose payload is a Tile class

whose payload is a class removeTile which only receives a string as payload, which is the ID

which only receives a string as payload, which is the ID updateTile which also receives a Tile class

Notice that props is a function that gets imported from @ngrx/store and gets called as a second argument.

Dashboard Reducer

In order to build the dashboard reducer, we will use the new factory method called createReducer that takes the following arguments:

the first argument is the initial state, that we created using the entity adapter

all the following arguments are the reducer functions for each action, that we define using the function on also imported from @ngrx/store

also imported from we use the entity adapter methods in order to add, remove and update the dashboard’s tiles

We import the reducer in the DashboardStoreModule :

@NgModule({

imports: [

StoreModule.forFeature('dashboard', dashboardReducer),

]

// more

Assets

As we are going to receive the list of assets using Coincap’s API, we’re just going to replicate their interface:

Assets Actions

In order to fetch the assets, we will need to perform an HTTP request to Coincap’s API. The HTTP action will be going through the effect method we’re going to define in the next article.

What’s important to notice here is the way I’ve broken up the assets’ actions:

getAssetsRequestStarted:

action that gets dispatched when the request starts

action that gets dispatched when the request starts getAssetsRequestSuccess:

action that gets dispatched when the request succeeded (no error actions in this case for simplicity, but you should always create them)

action that gets dispatched when the request succeeded (no error actions in this case for simplicity, but you should always create them) addAssets:

action that will only be used by the reducer, which is a command to add assets to the store

Assets Reducer and Adapter

The only reducer function reacting to the addAssets action will simply add all the assets to the store once they get fetched.

Prices

The prices returned by Coincap’s API are very simple and are just objects with the key of an asset and its relative price. As such, we have a very simple store for prices.

Prices Actions

We will be creating 3 actions:

addPrice:

action for updating the store once a price is received

action for updating the store once a price is received createPriceSubscription :

action for creating a subscription

: action for creating a subscription closePriceSubscription:

action for closing a subscription

Prices Reducer

As the prices returned by Coincap’s real-time API are simply a key with the asset and its price, we really don’t need to do much with the entity framework.

Indeed, for each price received, we simply set the key with the asset ID in our store and its price by spreading the price objects with the new payload.

If it doesn’t exist, it gets created, otherwise, it gets overwritten with its newest value.

Imagine our state is:

{ "bitcoin": "some price" };

And our payload from the WebSocket’s stream is:

{ "ethereum": "another price" }

This will simply become:

{

"bitcoin": "some price",

"ethereum": "another price"

};

And here is the code with one simple action:

An overview of the Store

Let’s take a look at the store with some data: