close icon
Angular

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.

January 03, 2019

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

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

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!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon