Schematics in Angular provide a way to automate common tasks, such as generating files, reading, and updating configuration files, and modifying your codebase. Angular Schematics are built using the Angular Devkit, which consists of many lower-level APIs, along with a few concepts to learn such as the Virtual Filesystem, Trees, Rules, Actions, Context and more. Learning these concepts and APIs can be challenging for developers looking to write schematics for Angular applications and libraries.
The Nx Devkit is an underlying part of the Nx build framework that provides utilities for building generators and executors. The Nx Devkit provides a way to build generators with a more straightforward approach using only JavaScript language primitives and immutable objects (the tree being the only exception). Combined with many higher-level utility functions, generators can be built with a few lines of code. Generators are similar to schematics in that they automate making file changes.
You may think that the Nx Devkit is only for Nx workspaces, but the Nx Devkit can also be used in a regular Angular CLI workspace. This guide walks you through adding the Nx Devkit to your Angular CLI workspace, using it to build an Nx Generator, and converting it to an Angular Schematic.
Creating an Angular CLI workspace
For this example, you’ll create a new workspace, and add a library.
First, create the Angular CLI workspace
npx @angular/cli@latest new nx-devkit-schematics
Next, generate a library within the workspace.
ng g my-lib
Adding Nx Devkit
To add the Nx Devkit, install the @nrwl/devkit package as a dev dependency,
npm install @nrwl/devkit --save-dev
OR
yarn add @nrwl/devkit --dev
Setting up the metadata and build
To create a generator, follow the same process as creating a schematic. Create a my-lib/schematics
folder and define a collection.json
with an entry for my-schematic. This metadata is how Nx Generators provide data on how they are to be processed.
{
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"my-schematic": {
"description": "Generate a service in the project.",
"factory": "./my-schematic/index#mySchematic",
"schema": "./my-schematic/schema.json"
}
}
}
Add a schematics property to the package.json
with a path to the collection, and build steps for compiling the TypeScript and copying assets to the dist directory.
{
"name": "my-lib",
"version": "0.0.1",
"scripts": {
"prebuild": "../../node_modules/.bin/rimraf dist/my-lib && mkdir -p ../../dist/my-lib/schematics && cp -R schematics/ ../../dist/my-lib/schematics/",
"build": "../../node_modules/.bin/tsc -p tsconfig.schematics.json",
"postbuild": "../../node_modules/.bin/rimraf --glob ../../dist/my-lib/schematics/**/*.ts"
},
"peerDependencies": {
"@angular/common": "^12.0.0",
"@angular/core": "^12.0.0"
},
"schematics": "./schematics/collection.json"
}
Also define a tsconfig.schematics.json
in the my-lib folder for compiling the generators.
{
"compilerOptions": {
"baseUrl": ".",
"lib": [
"es2018",
"dom"
],
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"rootDir": "schematics",
"outDir": "../../dist/my-lib/schematics",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"target": "es6",
"types": [
"node"
]
},
"include": [
"schematics/**/*"
],
"exclude": [
"schematics/*/files/**/*"
]
}
The scripts in the package.json
and configuration in the tsconfig.schematics.json
file are relative to the library, and are based on the library name and path.
Creating a simple generator
To create a generator, first define a schema. This has some information about the name of the generator, and properties for accepting arguments.
Create a schema.json
in the projects/my-lib/schematics/my-schematic folder.
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsMySchematic",
"title": "My Schematic Schema",
"type": "object",
"properties": {
},
"required": []
}
This schematic doesn’t define any properties or have any required fields.
Next, define the index.ts file inside the my-schematic folder with a very simple generator that logs out the provided options. A generator is just a function, with some metadata to define its inputs to produce outputs.
import { Tree, convertNxGenerator } from '@nrwl/devkit';
export function mySchematicGenerator(_tree: Tree, opts: any) {
console.log('options', opts);
}
The Tree
represents the virtual filesystem that gives you the same access to stage changes before actually applying them. Nx generator functions can also handle side effects, such as installing packages, formatting files, and more.
To convert this to an Angular Schematic, use the convertNxGenerator utility function.
export const mySchematic = convertNxGenerator(mySchematicGenerator);
This generator now functions the same as an Angular Schematic.
Building and Running the schematic
To build the schematic, first build the library
ng build my-lib
Then run the build script from the my-lib directory
npm run build --prefix projects/my-lib
Next, run the schematic from the command line
ng g ./dist/my-lib/schematics/collection.json:my-schematic
If you were to publish this library, it would work just the same as any other schematic.
ng g my-lib:my-schematic
You’ll see the options logged to the console when running the schematic.
Generating files
Another common task when using schematics is to generate files using templates. This can also be with a generator and a utility function provided by the Nx Devkit.
Let’s update the schema for the schematic to accept some options, such as the service name, the path to create the service, and the project.
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsMySchematic",
"title": "My Schematic Schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name",
"$default": {
"$source": "argv",
"index": 0
}
},
"path": {
"type": "string",
"format": "path",
"description": "The path to create the service.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
}
}
},
"required": [
"name"
]
}
Next, add a TypeScript interface for the JSON schema.
export interface Schema {
// The name of the service.
name: string;
// The path to create the service.
path?: string;
// The name of the project.
project?: string;
}
The name, path, and project are options the schematic accepts for the data service file.
Define a template for the generated file service named __name__.service__tpl__
in the projects/my-lib/schematics/my-schematic/files
directory. The __tpl__
denotes that the file is a template, and prevents the TypeScript code from being parsed. The double underscores also denote that these are template variables that are replaced when the file is generated.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class <%= className %>Service {
constructor(private http: HttpClient) { }
}
Update the generator to read the angular.json
file, build a path to where the files are placed, and scaffold the files using the generateFiles
function to replace the template placeholder variables.
import { Tree, convertNxGenerator, generateFiles, joinPathFragments, names, readJson } from '@nrwl/devkit';
export function mySchematicGenerator(tree: Tree, opts: any) {
const workspace = readJson(tree, 'angular.json');
if (!opts.project) {
opts.project = workspace.defaultProject;
}
const project = workspace.projects[opts.project];
const projectType = project.projectType === 'application' ? 'app' : 'lib';
if (opts.path === undefined) {
opts.path = `${project.sourceRoot}/${projectType}`;
}
const { className, fileName } = names(opts.name);
generateFiles(tree, joinPathFragments(__dirname, './files'), joinPathFragments(opts.path), {
className,
name: fileName,
tpl: ''
});
}
export const mySchematic = convertNxGenerator(mySchematicGenerator);
After building the schematics again, run the schematic to generate a service.
ng g ./dist/my-lib/schematics/collection.json:my-schematic my-data
The Nx Devkit contains all the functionality you need for writing Angular Schematics with a cleaner, high-level API. The Nx Devkit also has additional utilities for reading workspace and project configuration files, doing string replacements, and working with ASTs. By using language primitives, it makes authoringAngular Schematics easier to write, debug, and maintain.
See the full example here, and more documentation about the Nx Devkit can be found in the Nx Devkit guide.
Top comments (0)