developers

Create Your First Custom Angular CLI Schematic with Nx

Learn how to create apps, libs, and custom workspace schematics with the enterprise Angular tool Nx.

TL;DR In this tutorial, we’re going to learn the basics of Nrwl’s tool Nx, as well as how to create a custom workspace CLI schematic. You can see the finished code in this repository.

Note: This tutorial assumes some basic knowledge of Angular and the Angular CLI. If you've never touched Angular before, check out our Real World Angular series.

Concerns of Enterprise Teams

Developing applications on a large enterprise team is different than developing alone or on a small team. While small groups might be able to get away with ad hoc decisions on application structure or coding best practices, the same cannot be said when you’re working with dozens or perhaps hundreds of other people. In addition, enterprise teams need to assume that their code will stick around for many years to come. This means they’ll need to do as much work as possible at the beginning of an application’s development to minimize the cost of maintenance down the road.

We can summarize the concerns of enterprise teams like this:

  • Consistency — how do we make sure everyone in the organization (which may be thousands of people) follows the same best practices for structuring and writing code?
  • Safety — how do we ensure that our code will not be subject to attacks or prone to errors?
  • Increased size and complexity — how can we structure our code so that it can grow without sacrificing clarity or performance?
  • Changing requirements — how can we keep up with the demands of the business to continually update the application without letting technical debt get out of control?

While small teams and small organizations share these same concerns to some extent, the risks can be catastrophic at the enterprise scale. To learn more about the problems large teams face, check out Victor Savkin’s excellent ng-conf 2018 talk Angular at Large Organizations.

Nx: The Enterprise Toolkit for Angular

Angular Nx tool Logo

Source

Nx is a set of tools for the Angular CLI built by the consulting firm Nrwl to help with exactly these issues of consistency, safety, and maintainability. In addition to including a schematic to implement monorepo-style development of applications and libraries, Nx includes a set of libraries, linters, and code generators to help large teams create and enforce best practices across their organizations.

Nx by @nrwl_io is a set of tools to help enterprise @angular devs with consistency, safety, and maintainability.

Tweet This

Out of the box, Nx contains tools to help with:

  • State management and NgRx
  • Data persistence
  • Code linting and formatting
  • Migrating from AngularJS to Angular
  • Analyzing dependencies visually
  • Creating and running better tests
  • Creating workspace-specific schematics

While we can’t delve into every feature of Nx, we are going to take a look in just a bit at that last one: workspace-specific schematics. Before we do that, though, let’s learn how to get started with Nx.

Nx Basics

Let’s learn the basics of getting up and running with Nx. We’ll need to know how to install Nx, how to generate a workspace, and how to create applications and libraries.

Install Nx

We’ll start by installing Nx, which is really just a collection of Angular CLI schematics. You’ll need Node 8.9 or greater and NPM 5.5.1 or greater for this, which you can install from the Node website.

First, make sure you have the latest version of the Angular CLI installed globally:

npm i -g @angular/cli

(Note that

npm i -g
is just shorthand for
npm install --global
.)

Then, install Nx globally:

npm i -g @nrwl/schematics

Installing Nx globally is actually only required to create workspaces from scratch from the command line. If you’re unable to install things globally at work, don’t worry. You can add Nx capabilities to an existing CLI project by running this command:

ng add @nrwl/schematics

Note: You can also use Angular Console instead of the CLI throughout this tutorial if you'd prefer to work with a GUI instead of the command line.

Create a Workspace

Now that we’ve got Nx installed, we’re ready to create our first workspace. What exactly is a workspace, though? A workspace is an example of a monorepo (short for monorepository) — a repository that holds several related applications and libraries.

Let’s imagine we’ve been tasked to create a pet adoption system. Let’s think about some of the different pieces we might need for this:

  • A front end application for potential adopters to browse through the pets
  • A front end application for administrators to update available pets and receive inquiries
  • Shared UI components between the front ends
  • Shared data access libraries between the front ends
  • A Node server to serve up the pet and adoption inquiry data

While we could keep each of these in a separate repository, it would get messy keeping all of the versions in sync, managing dependencies, and ensuring that all developers working on the project have the correct access they need. Monorepos solve these problems and more.

With Nx, a workspace is a monorepo structure for the Angular CLI to keep your applications and libraries organized.

After installing Nx globally, we can run this command:

create-nx-workspace pet-adoption-system

When we run this command with version 7 or later of the CLI and Nx, we'll get two prompted questions. The first is whether we’d like to use a separate name for npm scope, which lets us import internally using a shorthand. For example, we could set the scope to "adoption-suite", which would mean our imports would start with

@adoption-suite
. We’re not going to do this in this tutorial, so we can just hit enter to leave it as the default, which is
pet-adoption-system
.

The second prompt is whether we’d like to use npm or Yarn for package management. I’ll be using npm in this tutorial, but you’re welcome to use Yarn instead.

This will create a new CLI workspace that will look a bit different from what you’re used to. Instead of the usual

src
folder, you’ll see folders named
apps
,
libs
, and
tools
at the root of the project. You’ll also see some extra files like
nx.json
to configure Nx,
.prettierrc
to configure the Prettier formatting extension, and
.editorconfig
to set some editor presets. Nx does a lot of set-up for you so you can focus on writing code instead of on tooling.

Create an Application

Let’s create an application in our new workspace and add routing to it.

ng generate app adoption-ui --routing

Version 7 of Nx adds prompts regarding directory placement, style extension, and choice of unit and end-to-end test runners. This is great because we can now easily set up an application using SCSS, Jest for unit testing, and Cypress for end-to-end testing with zero extra work. In this tutorial, feel free to leave all the prompts at their defaults. We won’t be using them here.

Nx’s

app
schematic is almost identical to the built-in CLI version, but it does have a couple of differences. For example, the
routing
option we added configures the root
NgModule
with routing instead of creating a separate module. This has increasingly become best practice in the Angular community to avoid "module pollution," so it’s really nice Nx does this for us by default.

After we’ve run the command, we’ll now have an

apps/adoption-ui
folder with the familiar CLI structure inside of it. Notice that the
AppModule
is set up with routing and that there is a separate
apps/adoption-ui-e2e
folder for Protractor. This differs a bit from the regular CLI setup, where the
e2e
folder is inside of the app folder.

Create a Library

Libraries are ideal places to house things like UI components or data access services for use in multiple applications.

We can add new libs to an Nx Workspace by using the AngularCLI

generate
command, just like adding a new app. Nx has a schematic named
lib
that can be used to add a new Angular module lib to our workspace:

ng generate lib shared-components

We’ll get several prompts at the command line regarding tooling, testing, setup, and routing. Since we’re not actually going to be using this library, the answers to these questions are irrelevant -- go ahead and just accept all of the defaults. However, it’s good to know how easily customizable libs are from the command line, from routing and lazy loading to tooling and setup.

Running this command will create a

libs/shared-components
with its own
src
folder and config files.

We can easily add components to this library by passing a

project
option to the CLI. Let's take advantage of the
g
shortcut for
generate
and the
c
shortcut
component
and run the following command:

ng g c pet-list --export=true --project=shared-components

Since this is a shared library, I’ve added

export=true
to export the component from the
NgModule
.

Now that we know the basics of Nx, let’s explore one of its lesser known but incredibly powerful features: the ability to create custom workspace schematics.

Creating a Custom Auth Schematic

You’re actually already familiar with schematics — they’re what the Angular CLI uses to create new components, services, and more when you run the

ng generate
command. You can also write your own schematics from scratch, whether or not you’re using Nx. The beauty of Nx, though, is that it does all the hard work of wiring up your custom schematics so that it’s easy to run them in your workspace.

Custom schematics are frequently used for two broad purposes:

  1. To enforce styles or standards at your organization.
  2. To generate custom code specific to your organization.

Custom @angular CLI schematics are used to enforce styles or standards and to generate custom code.

Tweet This

We’re going to focus on the former in this tutorial because generating custom code with the schematics API requires some knowledge of the TypeScript abstract syntax tree and is a little out of our scope in this tutorial.

Some examples of good ideas for custom schematics that enforce styles or standards are:

  • Enforcing directory structure or application architecture.
  • Enforcing patterns for data access like NgRx.

Since authentication is a subject dear to our hearts here at Auth0, let’s create a schematic for developers to use when adding an authentication module to their applications. We’d like it to do four things:

  1. Adhere to a naming convention of prefixing "auth-" to the files.
  2. Create an authentication module and import it into the project’s
    AppModule
    .
  3. Create an empty service that will hold our authentication code.
  4. Create an empty
    CanActivate
    guard that will hold an authentication route guard.

We can accomplish all of this in a single schematic that will accept the name of the module and the project we’re adding it to as arguments. This will let our developers working on the pet adoption system quickly add the correct scaffolding to any application they’re building on the project.

Let’s get started!

Generate the Custom Schematic

The first step to creating a custom schematic is to use Nx to generate the initial code. We can do this by running this command:

ng g workspace-schematic auth-module

(Notice that I've used the

g
shortcut for
generate
again.)

The

workspace-schematic
schematic (I know, it’s so meta) creates the
auth-module
folder inside of
tools/schematics
. This new folder contains two files:
schema.json
and
index.ts
.

Update the Schema

Schema.json
contains metadata for our custom schematic like the options used with it. If we open it, we’ll see this default code:

// tools/schematics/auth-module/schema.json
{
  "$schema": "http://json-schema.org/schema",
  "id": "auth-module",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Library name",
      "$default": {
        "$source": "argv",
        "index": 0
      }
    }
  },
  "required": ["name"]
}

By default, we’re able to pass a

name
property, which is a
string
, to our schematic. The
$default
parameter tells us that it will assume that the first argument given to this command is the value for the
name
property. Feel free to change the description of the
name
property to something more specific, like, "Auth module name."

Let’s also add another property to this file to specify the project to which we’ll add our new authentication module. Underneath the name property, add the following:

// tools/schematics/auth-module/schema.json
// ...above code remains the same
// add under the name property:
"project": {
  "type": "string",
  "description": "Project to add the auth module to"
}
// ...below code remains the same

Let’s make it required, too, by adding it to the

required
array. The finished file will look like this:

// tools/schematics/auth-module/schema.json
{
  "$schema": "http://json-schema.org/schema",
  "id": "auth-module",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Auth module name",
      "$default": {
        "$source": "argv",
        "index": 0
      }
    },
    "project": {
      "type": "string",
      "description": "Project to add the auth module to"
    }
  },
  "required": ["name", "project"]
}

We could add any other options with their types here if we needed them. If you look at

schema.json
for the official component schematic, for example, you’ll see familiar
boolean
options like
spec
and
inlineTemplate
.

Write the Custom Schematic

We’re now ready to write the custom schematic implementation. This lives in the generated

index.ts
file. Let’s open it up and begin to create our
auth-module
schematic. We’ll see the following code generated by Nx:

// tools/schematics/auth-module/index.ts
import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';

export default function(schema: any): Rule {
  return chain([
    externalSchematic('@nrwl/schematics', 'lib', {
      name: schema.name
    })
  ]);
}

Let’s break down what’s happening here so that we can build off of it.

First, we’re importing the

chain
and
externalSchematic
functions, as well as the
Rule
type, from
@angular-devkit/schematics
. We then use all of those imports in the exported function. What are these things, though?

We often say that schematics are like blueprints when you’re building a house. In that analogy, a

Rule
is a page or section of a blueprint. Rules are the individual pieces of the schematic that tell the CLI how to modify the file system, represented as a
Tree
in schematics. Trees allow us to mutate the file system with file creations, updates, and deletions. Generally, anything we can do with the file system we can do with the tree. Unlike the file system, though, trees use a transactional structure. No changes are made to the actual file system until the entire schematic runs. If part of the schematic fails, any changes to the file system are rolled back.

Rules, then, are functions that take in the current tree and return either a modified tree or another rule. This means that rules are also composable. We can combine rules using the

chain
function like we’re doing in this example. There are other operators, too, like
branchAndMerge
and
mergeWith
. You’ll see these functions throughout the official CLI schematics code.

We can also use the

externalSchematic
function here to refer to an outside schematic and compose its rules onto our custom schematic. In the boilerplate generated by Nx, we start with a rule that simply runs the
lib
schematic built into Nx, which is in the
@nrwl/schematics
collection.

The heart of the schematic is the exported function:

// tools/schematics/auth-module/index.ts
export default function(schema: any): Rule {
  return chain([
    externalSchematic('@nrwl/schematics', 'lib', {
      name: schema.name
    })
  ]);
}

This function takes in a

schema
and returns a
Rule
. The
schema
should match the structure in
schema.json
. At the core, all schematics are just functions that return rules that modify the file system. You’ll often see many rules defined in a schematic, as well as helper functions, but in the end, only one function gets exported. This function will have any necessary rules chained, branched, or merged together.

The simplest possible custom schematic is one that takes advantage of chaining together existing rules. This is particularly useful at large organizations to enforce best practices and architecture patterns. For example, you may want to ensure that everyone on the team always generates components with inline styles and with an accompanying model as a TypeScript interface. You could easily chain together the

component
schematic with the
interface
schematic to make this easy for everyone in the organization to do.

We’re going to follow a similar pattern here. We want to make sure that everyone who adds authentication to a project always adds a separate module, a service, and a

CanActivate
guard. We’ll use our new tools—the
chain
and
externalSchematic
functions—to do this.

Let’s first replace Nx

lib
with a call to the
module
schematic, keeping it inside of the array being passed into the
chain
function:

// tools/schematics/auth-module/index.ts
// ...above code remains the same
// replace nx lib with this:
externalSchematic('@schematics/angular', 'module', {
  project: schema.project,
  name: schema.name,
  routing: true,
  module: 'app.module.ts'
})

Notice that we’re first passing in the collection (

@schematics/angular
), followed by the schematic name (
module
), followed by an options object. The options object contains values for any options for the external schematic. We’re passing in the project, name, and root module name of
app.module.ts
. We’re also setting the
routing
flag to
true
to add routing to the authentication module. This makes it easy for someone to add routes to the authentication module, such as a
callback
route or routes for logging in and out.

For the calls to the

service
and
guard
schematics, we’ll need to import
path
at the top of our file:

// tools/schematics/auth-module/index.ts
// ...previous imports
import * as path from 'path';

This will let us specify file paths regardless of whether the user is using a Windows or Linux-based operating system. Add the following to the array after the

module
schematic (don’t forget a comma!):

// tools/schematics/auth-module/index.ts
// ...above code remains the same
externalSchematic('@schematics/angular', 'service', {
  project: schema.project,
  name: schema.name,
  path: path.join(
    'apps',
    schema.project,
    'src',
    'app',
    schema.name,
    'services'
    )
})
// ...end of the array and chain function

When we run this schematic, it will generate our service inside of

apps/{project}/src/app/{schema}/services
. You could easily change this to a different structure if you’d like. Do you notice how easy these schematics make standardizing and enforcing code organization?

Our last call is to the

guard
schematic and it’s almost identical. Add this after the
service
schematic (and, again, don’t forget the comma!):

// tools/schematics/auth-module/index.ts
// ...above code remains the same
externalSchematic('@schematics/angular', 'guard', {
  project: schema.project,
  name: schema.name,
  path: path.join(
    'apps',
    schema.project,
    'src',
    'app',
    schema.name,
    'services'
    )
})
// ...end of the array and chain function

This will generate a guard in the same place as the authentication service.

Our schematic is nearly finished now. It will create a module, service, and guard with the name we give it and for the project we specify. Let’s add one final touch: let’s throw an error if the user doesn’t prefix their new authentication module with

auth-
.

To do this, we can add the following

if
statement inside of our function on line 5, just before we return our chain of rules:

// tools/schematics/auth-module/index.ts
// ...above code remains the same
// add above the returned chain function:
if (!schema.name.startsWith('auth-')) {
  throw new Error(`Auth modules must be prefixed with 'auth-'`);
}
// ...below code remains the same

We’ll now see an error if we don’t adhere to the naming guidelines. Neat!

The finished code for our custom schematic looks like this:

import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';
import * as path from 'path';

export default function(schema: any): Rule {
  if (!schema.name.startsWith('auth-')) {
    throw new Error(`Auth modules must be prefixed with 'auth-'`);
  }

  return chain([
    externalSchematic('@schematics/angular', 'module', {
      project: schema.project,
      name: schema.name,
      routing: true,
      module: 'app.module.ts'
    }),
    externalSchematic('@schematics/angular', 'service', {
      project: schema.project,
      name: schema.name,
      path: path.join(
        'apps',
        schema.project,
        'src',
        'app',
        schema.name,
        'services'
      )
    }),
    externalSchematic('@schematics/angular', 'guard', {
      project: schema.project,
      name: schema.name,
      path: path.join(
        'apps',
        schema.project,
        'src',
        'app',
        schema.name,
        'services'
      )
    })
  ]);
}

Remember that we’re only generating the scaffolding of our authentication setup with this schematic. To learn how to properly implement authentication in your service and guard, check out our Angular authentication tutorial. We’ve also got an NgRx authentication tutorial for you if you’re taking advantage of NgRx in your project. To use either of these tutorials to set up Auth0 in your application, first sign up for a free Auth0 account here.

Run the Custom Schematic

Now that we’ve got the schematic written, let’s test it out. Nx already did the work of wiring it up to be used in our workspace, so we don’t need to worry about that. To run our new

auth-module
schematic, run the following command:

npm run workspace-schematic -- auth-module auth-adoption --project=adoption-ui

This command runs the

workspace-schematic
script that’s part of Nx. We use the
--
operator to pass options into that script like the names of the module and project. (If you’re using Yarn, you can ditch the
--
operator and run
yarn workspace-schematic auth-module
with the rest of the options.)

Once the schematic runs, we’ll see the resulting files inside of the

src
folder of the
adoption-ui
application. Our
auth-adoption
module and its routing module will be there, as well as the
services
folder containing the
auth-adoption
service and route guard.

Don’t forget to also test out our naming requirement. Try to run the command again but without the

auth-
prefix. You should see
Auth modules must be prefixed with 'auth-'
as an error in your console. You’ll also see an error if you fail to specify the project name.

All of the finished code can be found in this repository.

Conclusion

Our custom authentication schematic accomplishes a lot with under 50 lines of code:

  • It automatically scaffolds an authentication module, service, and route guard.
  • It enforces both an architecture standard and a naming standard.
  • It can easily be reused by developers for future products.

Since writing custom schematics for the first time can be a bit of a challenge, it helps to have Nx do a lot of the heavy lifting. That’s really what Nx does best: automating and simplifying Angular development at scale.

To learn more about custom schematics, check out the introduction on the Angular blog, this great tutorial on Generating Custom Code by Manfred Steyer, and this presentation on custom schematics by Brian Love. And, of course, the best place to see examples of custom schematics is the source code for the Angular schematics.

We’ve really only scratched the surface of what Nx can do. To learn more about Nx, including features like the visual dependency graph, dependency constraints, and the ability to run only affected tests, check out the official Nx documentation, the nx-examples repo, and this free video course on Nx by Justin Schwartzenberger.

Special thanks to Jason Jean from Nrwl for doing some pair programming with me to answer my questions about Nx and custom schematics. Thanks, Jason!