
Getting Started with Nx: The Nrwl Extensions for Angular
Reading Time: 9 minutesIn this article we will learn how to create an Angular Nx Workspace and scaffold it out with multiple apps and libs in a mono-repo style approach.
What is Nx
Nx is an open source toolkit for enterprise applications that is created and maintained by the team at Nrwl. It's based on their experience working at Google and helping the Fortune 500 build ambitious Angular applications.
Nx is an extension for the Angular CLI and is made up of the following:
- A set of schematics for project and code generation (@nrwl/schematics)
- A helper library for handling common tasks like data persistence with NgRx and testing (@nrwl/nx)
- A set of binaries that can be run from the terminal to help with formatting, linting and code base analysis
Learn more about Nx by visiting nrwl.io/nx
An Nx Workspace is an Angular CLI project configured to make working with multiple Angular applications and libs within a single repo more reliable. It also helps facilitate the architecture approach of having a thin layer at the app level (an app NgModule that handles configuring things) and the rest in libs that can either be app specific like feature modules or reusable across apps like component libraries or utilities and services.
Getting Up and Running with an Nx Workspace
Let's walk through setting up the initial architecture structure for a workspace. Say we are creating a platform for a podcast show. We plan to have a user facing app that provides info about the podcast as well as a list of episodes, and we plan to have an admin app for managing the podcast show information. We've also identified that there will be components and logic that will be common between both of these apps.
Step 1: Create an Nx Workspace
Since Nx is an extension for the Angular CLI, the creation of an Nx Workspace involves either creating a new CLI project or adding Nx to an existing one. The current major version of Nx (6.x) aligns with the current version of the Angular CLI (6.x). So creating an Nx Workspace involves using the Angular CLI.
There are two ways to go about creating a new Nx Workspace, using a binary that is provided by @nrwl/schematics
or using the Angular CLI ng new
command. The binary is the current recommended way as the ng new
command requires some additional steps. But we'll walk through both here.
Generate a New Project: The create-nx-workspace
Command
The @nrwl/schematics
library contains a binary script that supports the command create-nx-workspace
. We can make use of that by first installing the @nrwl/schematics
globally. Now, since an Nx Workspace is an Angular CLI workspace under the hood, we will most likely want to install the Angular CLI globally as well. We can do both with the following command:
npm install -g @nrwl/schematics @angular/cli
With these installed, we can make use of the create-nx-workspace
command to create a new Nx Workspace:
create-nx-workspace podcast-platform
This will create a new Nx Workspace folder named podcast-platform
in the current working directory (place where you ran the command) with no apps or libs to start...just the structure in place. We will see how we can add apps in the next step.
Generate a New Project: The ng new
Command
The create-nx-workspace
handles running the ng new
command along with the options it needs to leverage the Nx schematics for creating a new workspace. If we wanted to, we could do that process manually. But, as of the writing of this post, there is a minor issue that requires a workaround with Nx when creating a new workspace. We need to install the NgRx schematics globally before running the ng new
command. This can be done with the following command:
npm install -g @ngrx/schematics
With that installed, we can use the Angular CLI new
command and to create our new Nx Workspace:
ng new podcast-platform
--collection=@nrwl/schematics
--directory=podcast-platform
The --collection
option is used to tell the Angular CLI that we want to use the Nx schematics collection and its new
command. The --directory
option is used to tell the Angular CLI what directory to put the workspace files in.
So we can do this process manually, but if we use the create-nx-workspace
command then all of the above is handled for us!
... or Add Nx to an Existing Project
The Angular CLI (version 6.x and above) has an add
command that can be used to add stuff to an existing Angular CLI workspace. If we already have a workspace set up (and that only has 1 app project in it), we can use the add
command to add the @nrwl/schematics
to it and it will handle installing the dependencies needed and moving files around to match the structure of an Nx Workspace:
ng add @nrwl/schematics
Step 2: Generate Angular Applications
The Nx schematics collection has a schematic named app
for adding a new Angular application project to the workspace. It has several options (which we can see if we run ng generate app --help
) that can be used when creating a new app. Let's use that to create the user facing app and the admin app in our Nx Workspace, and we'll make use of the routing
flag that will add the initial bits for setting up routing (the router module, a default route, and a router outlet).
We can run the following generate command to add the user facing app (which we'll name website
):
ng generate app website --routing
And we can run the following generate command to add the admin app (which we'll call admin
):
ng generate app admin --routing
Running these will result in four new projects created within the angular.json
file (the two apps and an end to end test suite, or e2e, for each of them).
{
. . .
"projects": {
"website": {
. . .
},
"website-e2e": {
. . .
},
"admin": {
. . .
},
"admin-e2e": {
. . .
}
},
. . .
}
And it will put the source code for those projects in the apps
directory in the workspace.
That source code will be similar to the app code the Angular CLI sets up for a new project with the addition of that routing code we asked it to add for us with the --routing
flag.
Step 3: Generate Libs
Libs in an Nx Workspace are designed to be the home for all of your application code outside of its setup and configuration. So things like feature modules, routed modules, shared modules, reusable component modules, etc. And these can sit directly in the libs directory or they can be nested to help organization and reasoning.
Let's create some libs for the code that will power our podcast applications. The Nx schematics collection has a schematic named lib
that can be used to create new libs. Just like the app
schematic, the lib
schematic has several options which we can see if we run ng generate lib --help
. We will make use of some of the routing options and the directory option when we create our libs.
Feature Libs (Routed)
We can begin with the bits for the user facing app (website
). Say we want to have a home screen and an episode details screen and we want to route to those and make the episode details screen be lazy loaded. And these screens will be specific to the website
app.
We can add a lib for the home screen like so:
ng generate lib home-screen
--directory=website
--routing
--parent-module=apps/website/src/app/app.module.ts
With this command we are doing the following:
- Naming the lib
home-screen
which will create an NgModule by that name - Using the
directory
flag to create a nested directory in thelibs
directory with the name website to help us organize libs that are specific to the website app - Using the
routing
flag to indicate that this lib will be a routed module - Using the
parent-module
flag in conjunction with therouting
flag to tell it which existing NgModule to add this new lib as a route entry to
And then we can add the lib for the episode details like so:
ng generate lib episode-details
--directory=website
--routing
--parent-module=apps/website/src/app/app.module.ts
--lazy
With this command we are doing the same as the previous lib we added with the addition of the following:
- Specifying that we want this routed lib to be lazy loaded with the
lazy
flag, which will set up the route entry with theloadChildren
property and string value to make it lazy loaded
The lib
schematic will also take care of some plumbing in the workspace config files to make it so we can import from these libs using an npm scope shorthand (like @podcast-platform/website/home-screen
) as well as "barrel files" for controlling what we want to export from these libs as usable by other libs and apps.
{
"compilerOptions": {
. . .
"paths": {
"@podcast-platform/website/home-screen": [
"libs/website/home-screen/src/index.ts"
],
"@podcast-platform/website/episode-details": [
"libs/website/episode-details/src/index.ts"
]
}
},
. . .
}
For the admin
app we can do a similar approach to creating the new libs. Say we want a dashboard screen, a lazy loaded screen for episodes and one for guests. We can run the following:
ng generate lib dashboard
--directory=admin
--routing
--parent-module=apps/admin/src/app/app.module.ts
ng generate lib episodes
--directory=admin
--routing
--parent-module=apps/admin/src/app/app.module.ts
--lazy
ng generate lib guests
--directory=admin
--routing
--parent-module=apps/admin/src/app/app.module.ts
--lazy
Shared and Reusable Libs
With our apps and feature libs in place, we can focus on scaffolding out the libs that we intend to share across apps. Say we wanted to create some presentational components to display our podcast data in a common way. We could create a common-ui
lib for that stuff.
ng generate lib common-ui
This will create a CommonUiModule
that we can use that will declare those components that we can use within the feature modules for the apps. One set of code to maintain between apps, and ready for reuse if we add more apps in the future.
Now, we could go and manually add that component code that we need to this lib by creating the files in the libs/common-ui/src/lib
dir, but the Angular CLI provides schematics for generating that code. And those schematics support an option flag named project
that can be used to tell it which project to generate it in. Apps are created as project entries, and so are libs. So we can use the --project
flag to add a new component to the common-ui
lib:
ng generate component episode-summary
--project=common-ui
--export
Then from there we can add the CommonUiModule
to the imports array in the NgModule metadata for the other libs where we intended on using it:
. . .
import { CommonUiModule } from '@podcast-platform/common-ui';
@NgModule({
. . .
imports: [
. . .
CommonUiModule
]
})
export class HomeScreenModule {}
With that --project
flag, we can use the Angular CLI schematics to add components, directives, services and pipes to any of the libs that we create.
And with the barrel files (the index.ts
files in each lib) we can control what bits we intend to be able to use outside of those libs (make public). So if we wanted to make a service that formats the podcast data in a certain way for that episode summary component but we don't intend for that service to be re-used outside of that component we can simply leave it out of the exports in the index.ts
file. Nx has custom linting rules that will help enforce code boundaries, so attempting to deep import that service will result in an error in both our IDE/editor as well as the build.
Where to Go From Here
We can use the Angular CLI schematics to scaffold out the components, directives, sub modules and more throughout our libs and we can write specs within those libs for that code (and can run lib specific tests by using the ng test <project-name>
command). If we have multiple teams, each team can own a lib (or libs) and have a manageable way to work that code base.
We can serve/build individual apps by using the project name along with those commands (ng serve admin
or ng serve website
). And we can configure the port to use for each app in the angular.json
file so that we can serve up each app concurrently (via the projects/<project-name>/architect/serve/options/port
property in the json object).
Beyond the basic application scaffolding and development workflow, an Nx workspace has a set structure and pattern to it, and because of this Nx is able to analyze the code base and identify if the architecture and usage is as expected and understand the layout and dependencies of it. In addition to the custom linting rules mentioned earlier, Nx has commands to help auto-format your code (using Prettier), display a visual dependency graph of your Angular modules, and even provide the opportunity to tag library types in the nx.json
file so we can specify intent and get notified (via linting rules set up in tsconfig.json
) when we attempt to use lib code in an unintended way.
To learn more about what Nx provides visit the documentation at https://nrwl.io/nx/guide-nx-workspace.
If your company or team is experiencing challenges with architecting and engineering Angular applications at scale, the team at Nrwl can help! Visit https://nrwl.io to learn more and to get in touch.
About the Author
Justin Schwartzenberger is a Google Developer Expert for Angular and Web Technologies, the host of the weekly AngularAir live video broadcast, author of online Angular training courses, a frequent conference speaker, and an open source contributor. You can follow Justin on Twitter @schwarty and find out more about him at schwarty.com.