Ngrx In Monorepo
Shared store library and 2 different apps using it
This project was generated with Angular CLI version 14.2.3.
Posted on January 16, 2023
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.
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.
nx g @nrwl/angular:ngrx --module=libs/shared-store/src/lib/shared-store.module.ts
Generate NgRx store in the NX Angular application, for example for our product-shop:
nx g @nrwl/angular:ngrx product-shop --root
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.
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');
AppModule
of Product Rent Agency:
providers: [
{ provide: API_TOKEN, useValue: '/product-rent-agency/api' }
]
AppModule
of Product Shop:
providers: [
{ provide: API_TOKEN, useValue: '/product-shop/api' }
]
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:
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:
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:
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:
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:
Shared store library and 2 different apps using it
This project was generated with Angular CLI version 14.2.3.
Posted on January 16, 2023
Sign up to receive the latest update from our blog.