How to set up an Nx-style monorepo workspace with the Angular CLI: Part 2
In Part 2 of this tutorial, we'll set up our custom generate project tool and create the shared and booking data access libraries with NgRx. To honor the flow of dependencies, we extract a shared environments library.
Lars Gyrup Brink Nielsen
inDepth.dev Writer, Tech Speaker, Podcast Host, OSS Contributor, Microsoft MVP.
Frontend Architect at Systemate in Denmark.
Passionate about software architecture, testing, and reactive programming.
This tutorial is part of the Angular Architectural Patterns series.
In Part 1 of this tutorial, we set up the booking desktop application project, a project for its end-to-end test suite, and the booking feature shell workspace library.
In this part, we'll set up our custom generate project tool to automate the steps we did manually in Part 1. We'll use it to create the shared and booking data acess libraries with NgRx Store, NgRx Effects, NgRx Schematics, and NgRx Store DevTools.
To configure the data access libraries while keeping the flow of dependencies correct, we'll extract a shared environments library. Data access will be hooked up to the booking feature shell library.
Generate project tool
To generate the rest of the workspace libraries, we are going to use commands very similar to the ones we just saw.
Instead of copy-pasting snippets, let's create a script to generate a workspace library. We could have created an Angular CLI schematic for this, but we'll use a Node.js script for simplicity.
We'll use the package yargs for parsing command line arguments passed to our script.
Create a tools folder with a filed named generate-project.js.
The tool basically runs commands demonstrated in the earlier sections with a few tweaks. We could have created a schematic, but I wanted you to be able to see the similarities without knowing about the complicated nature of schematic implementations.
yargs is used to setup up commands and parameters for the tool.
When we run node ./tools/generate-project.js, we get output similar to the following.
Let's set up an NPM script for our tool in package.json.
Booking data access library
Now we're ready to try out the generate project tool. Run the following commands to generate the booking data access library.
The generated file and folder structure is shown in this figure.
Like our other workspace library, this one is configured with test and lint architect targets.
You know the drill. Run the commands in this listing to try them out.
Booking state with NgRx
Let's add application state for the booking domain.
First, we install the NgRx packages we're going to use by running the previous commands.
Next, we're going to generate application state management with NgRx Store and NgRx Effects as seen in this listing.
This generates the folder and files illustrated in the following figure inside libs/booking/data-access/src/lib.
The NgRx feature schematic registered our booking feature store and effects with the booking data access module as per this listing.
Finally, we'll register it in the booking feature shell module as shown in the following listing.
Shared data access library
Let's move on to the shared data access library. This workspace library will be shared between all the booking and check-in applications.
Use the previous commands to generate the shared data access library. By now, you know what this generates and configures.
A shared data access library sets up root level configuration for application state management and data services. This could be adding HTTP interceptors for security and API paths.
In our case, we'll initialise the root store and effects of our applications.
First, we install the NgRx Store development tools package with the previous commands.
The previous listing shows how to generate and register the root store in the shared data access library. Unfortunately, we stumble upon a few issues when doing this, at least at the time of writing.
There are a few problems in the generated code shown in the previous listing.
First, a comma is missing after the import of StoreModule.forRoot. This is easily fixed as per this listing.
Using the environment configuration in a workspace library
The final problem we need to solve is that because our registration is done in a workspace library, we don't have access to the environment file which is usually in an application project.
We don't have path mappings that we can use to import from an application project. We shouldn't add them either! Workspace libraries must never depend on application projects – only the other way around.
As we don't expect any differences between our application environment settings, we'll make the library shared between all application projects.
After running the previous commands, our environments library has the file and folder structure shown in this figure.
Because we moved the environment file, we have to update the fileReplacements option for the production configuration of our application's build architect target. This is shown in the following listing.
Now we're ready to correct the dependency in our shared data access module as seen in this code listing.
Don't forget to update the import statement in the booking desktop application's main.ts file. This is shown here.
To use the shared data access library in the booking desktop application, we need to register our shared data access module in the booking feature shell module.
Note that we register the shared data access module before the domain-specific data access module BookingDataAccessModule as Angular modules import order matters. For example, the root store needs to be registered before any feature store.
Let's also generate root effects with the commands in the listing above. This will register the root effects in the shared data access module as seen in this listing.
Our shared data access library ends up having the file and folder structure displayed in the following figure.
If we lint the workspace library now, we have a few code smells to address in the generated code. I'll leave this as an exercise for you to do. Consider circling back and doing the same for the booking data access library.
Start the application by running the ng run booking-desktop:serve command.
The previous screenshot shows how the booking desktop application looks with the NgRx Store DevTools open.
At this point, our workspace has project folders as seen in the previous figure.
In Part 2, we started by setting up our custom generate project tool. It automated the steps we did manually to generate application, end-to-end, and workspace library projects in Part 1.
First, we used the tool to generate the booking data access library. We then installed NgRx Store, NgRx Effects, and NgRx Schematics to our package dependencies.
We used the NgRx Schematics to generate a +state folder in our workspace library with NgRx feature effects, actions, reducers, and selectors.
To register the feature state in the booking desktop application, we imported the booking data access Angular module in the booking feature shell Angular module.
After the booking data access library, we generated the shared data access library which is intended to configure and initialise the root NgRx state and effects for both the booking and check-in (still to come) applications.
We added the NgRx Store DevTools package, used the NgRx schematics to create a +state folder with root reducers, meta reducers, root effects and configuration of runtime checks and NgRx Store DevTools instrumentation.
Some of the configuration needed to know whether the application was running in the development or production mode. This state is usually defined in the environment object of the application project.
Since the shared data access is a library project, it must not depend on an application project. Because of this, we used a recipe from the article "Tiny Angular application projects in Nx workspaces" to extract a shared environments library. We set up file replacements in our builders.
With this in place, both our booking desktop application project and the shared data access library project was able to depend on the environment configuration.
We hooked everything up by registering data access in the booking feature shell Angular module. As we saw in the screenshot in this conclusion, NgRx Store DevTools shows us that everything is set up correctly.
That's it for Part 2 of this tutorial. In Part 3, we'll create the passenger info and flight search feature libraries with routing and set up the mobile booking application project and its end-to-end test project and create a mobile-specific template for the flight search component.