Nx Devkit and Angular Devkit

Note: this document covers the difference between Nx Devkit and Angular Devkit. See the Nx Devkit guide for more in-depth details about Nx Devkit.

Nx comes with a devkit to write generators and executors, but you can also use Angular devkit (schematics and builders). In other words, you can use an Angular schematic to implement a generator, and you can use an Angular builder to implement an executor.

What are the differences between Nx Devkit and Angular Devkit?

Generators

The following is a generator written using Nx Devkit:

1import { Tree, formatFiles, generateFiles } from '@nrwl/devkit';
2import * as path from 'path';
3
4interface Schema {
5  name: string;
6  skipFormat: boolean;
7}
8
9export default async function (tree: Tree, options: Schema) {
10  generateFiles(
11    tree,
12    path.join(__dirname, 'files'),
13    path.join('tools/generators', schema.name),
14    options
15  );
16  if (!schema.skipFormat) {
17    await formatFiles(tree);
18  }
19}

The following is an analogous generator written as an Angular Schematic.

1import {
2  apply,
3  branchAndMerge,
4  chain,
5  mergeWith,
6  Rule,
7  template,
8  url,
9  move,
10} from '@angular-devkit/schematics';
11import { formatFiles } from '@nrwl/workspace';
12import { toFileName } from '@nrwl/workspace';
13
14interface Schema {
15  name: string;
16  skipFormat: boolean;
17}
18
19export default function (options: Schema): Rule {
20  const templateSource = apply(url('./files'), [
21    template({
22      dot: '.',
23      tmpl: '',
24      ...(options as any),
25    }),
26    move('tools/generators'),
27  ]);
28  return chain([
29    branchAndMerge(chain([mergeWith(templateSource)])),
30    formatFiles(options),
31  ]);
32}

Notable Differences

  • Nx Devkit generators do not use partial application. An Angular Schematic returns a rule that is then invoked with a tree.
  • Nx Devkit generators do not use RxJS observables. Instead you invoke the helpers directly, which makes them more debuggable. As you step through the generator you can see the tree being updated.
  • There are more affordances for commonly used operations. For instance, chain([mergeWith(apply(url is replaced with generateFiles)
  • Nx Devkit generators return a function that performs side effects. Angular Schematics have to create a custom task runner and register a task using it.
  • Nx Devkit generators are composed as any other JS function. You do need to go through a special resolution step (externalSchematic) that is required when using Angular Schematics.
  • No special utilities are needed to test Nx Devkit generators. Special utilities are needed to test Angular Schematics.

Conversions

The Nx CLI can invoke Nx Generator or Angular Schematics directly. When the user runs:

nx g mygenerator params
ng g mygenerator params  # will work exactly the same same as the line above

The Nx CLI will see what type of generator mygenerator is and will invoke it using the right machinery. The user doesn't have to know how the generator is implemented.

At times, however, it might be useful to use an Nx Devkit generator in an Angular Schematic or vice versa.

Making an Angular Schematic out of Nx Devkit Generator:

First, you need to

1export async function mygenerator(tree: Tree, options: Schema) {
2  // ...
3}
4export const mygeneratorSchematic = convertNxGenerator(mygenerator);

Then, you might need to register it in the collections.json:

1{
2  "name": "Nx React",
3  "version": "0.1",
4  "extends": ["@nrwl/workspace"],
5  "schematics": {
6    "mygenerator": {
7      "factory": "./src/generators/mygenerator/mygenerator#mygeneratorSchematic",
8      "schema": "./src/generators/mygenerator/schema.json"
9    }
10  },
11  "generators": {
12    "init": {
13      "factory": "./src/generators/mygenerator/mygenerator#mygenerator",
14      "schema": "./src/generators/mygenerator/schema.json"
15    }
16  }
17}

Making an Nx Devkit Generator out of Angular Schematic:

1export const libraryGenerator = wrapAngularDevkitSchematic(
2  '@nrwl/angular',
3  'library'
4);
5
6export async function mygenerator(tree: Tree, options: Schema) {
7  await libraryGenerator(tree, options);
8}

Executors

The following is an executor written using Nx Devkit:

1interface Schema {
2  message: string;
3  allCaps: boolean;
4}
5
6export default async function (
7  options: Schema,
8  context: ExecutorContext
9): Promise<{ success: true }> {
10  if (options.allCaps) {
11    console.log(options.message.toUpperCase());
12  } else {
13    console.log(options.message);
14  }
15  return { success: true };
16}

The following is an analogous executor written as an Angular builder:

1interface Schema {
2  message: string;
3  allCaps: boolean;
4}
5
6export default function (
7  options: Schema,
8  context: BuilderContext
9): Observable<{ success: true }> {
10  if (options.allCaps) {
11    console.log(options.message.toUpperCase());
12  } else {
13    console.log(options.message);
14  }
15  return of({ success: true });
16}
17export default createBuilder<NextBuildBuilderOptions>(run);

Notable Differences

  • Nx Devkit executors return a Promise (or async iterable). If you want, you can always convert an observable to a promise or an async iterable. See Using Rxjs Observables
  • Nx Devkit executors do not have to be wrapped using createBuilder.

The schema files for both Nx Devkit executors and Angular Builders are the same. Nx can run both of them in the same way.

When to Use What

If you are writing an Nx plugin, use Nx Devkit. It's much easier to use and debug. It has better docs and more people supporting it.

Do you have to rewrite your Nx Plugin if it is written using Angular Devkit? No. Nx supports both and will always support both. And, most importantly, the end user might not even know what you used to write a generator or an executor.