DEV Community

Cover image for NX Angular monorepo and shared NgRx store
Daniil Rabizo
Daniil Rabizo

Posted on • Originally published at Medium

NX Angular monorepo and shared NgRx store

THE ARCHITECTURAL GUIDE FOR SHARED STORE IN ANGULAR NX MONOREPO

In a project setting, where you have multiple Angular apps in a monorepo and some shared NgRx state, which these apps use, you would definitely want to avoid duplicating the store code in each app. For that you should create an NX library and extract the reusable part of the store there.

In this guide, I will demonstrate an example of 2 different apps: “Product Shop” and “Product Rent Agency”, which share the same Products state, therefore use the shared product store library and extend it with their own actions, effects, reducers and selectors.

The “Product Shop” and “Product Rent Agency” both have to fetch all products, so we need a FETCH action, effect and reducer, and this is the reusable code, we want to extract to the shared store library.

However, “Product Shop” can also SELL products, and “Product Rent Agency” can RENT products, which are completely different actions and require their own NgRx logic.

NX store library architecture

We create an NX library for the reusable parts of the store and create a store in each app as well. Each concrete store is going to extend a shared store library.

The shared library should not know any implementation details about concrete apps using it.

For that we are going to use Injection Token for an API URL, and import shared store parts in the concrete applications, but first let’s start with creating an NX library.

Add a shared store library



nx g @nrwl/angular:ngrx --module=libs/shared-store/src/lib/shared-store.module.ts


Enter fullscreen mode Exit fullscreen mode

Add a store to each concrete app as well

Generate NgRx store in the NX Angular application, for example for our product-shop:



nx g @nrwl/angular:ngrx product-shop --root


Enter fullscreen mode Exit fullscreen mode

Create a module for each app and register Effects and Store there, as we did in Product Shop application:

registering store for Product Shop app:

registering store for Product Rent Agency app:

Note that each app should use the same PRODUCTS_FEATURE_KEY, so that we can register Store with this key, and shared store actions are triggered from concrete applications.

Create an API token

This is important to isolate shared library from knowing the details about outside applications.

It still has to perform API calls with different URLs, but not know the details about Product Shop or Product Rent Agency applications. So we have to provide an API TOKEN in concrete applications with its own API URL



/**
 * necessary for generic effects, calling different API endpoints for each app
 */
export const API_TOKEN = new InjectionToken('apiToken');


Enter fullscreen mode Exit fullscreen mode

AppModule of Product Rent Agency:



providers: [
    { provide: API_TOKEN, useValue: '/product-rent-agency/api' }
]


Enter fullscreen mode Exit fullscreen mode

AppModule of Product Shop:



providers: [
    { provide: API_TOKEN, useValue: '/product-shop/api' }
]


Enter fullscreen mode Exit fullscreen mode

Actions

Each application has to fetch products, so we create reusable product actions in the NX library:

actions for fetching products:

And “Product Shop” app has its own concrete actions to SELL products:

And “Product Rent Agency” can RENT products:

Effects

We are using API_TOKEN here so that each application can call its own different API URL, even while using shared ProductsEffects

Calling API to fetch products with an API_TOKEN:

We can define any logic in concrete Effects we want, for example in Product Rent Agency we Rent the product and call the respective API for that:

In Product Shop we also might want to have some effects, for example to sell the product with an API call:

Reducers

Make sure to export shared reducers as an array, in order to import and reuse them later in concrete apps. In this example we export productReducers, which we are going to import and use with help of spread operator ...productReducers in concrete reducers in Product Shop and Product Rent Agency.

Shared Product Reducers:

Reducers of Product Shop for updating its state when selling products, and imported productReducers with spread operator:

Reducers of Product Rent Agency, and imported productReducers with spread operator:

Selectors

There are reusable selectors which select the shared part of the state, as well as concrete selectors for concrete state parts. Both shared and concrete use the same PRODUCTS_FEATURE_KEY to access the state. This feature key is the only thing that is binding these selectors, so they are loosely coupled.

For example shared state has the isLoading flag, which we want to use in both apps, so let’s define a shared selector for that:

And some selectors to select the concrete state of Product Rent Agency, let’s assume we want to select all Products and the number of rented items, which is part only of Product Rent Agency:

And for Product Shop we want to select revenue, which exists only in Product Shop:

Summary

With this implementation we have reduced code duplication for the same NgRx logic, that both apps use, by extracting it into the NX library.

See the whole implementation of an NX shared store library and two different apps inside the NX monorepo in my GitHub:

GitHub logo dnlrbz / ngrx-in-monorepo

Using shared NgRx store in NX Angular monorepo




Top comments (0)