Different ways to run schematics from another schematics

Use Cases

  1. Repeated pattern across files based on existing boiler-plate
    This can be specific to your team’s project, where you want everyone to follow the same boilerplate. And you want to create boiler-plate on top of existing ones. @maciej_wwojcik has written great article about it: Extend Angular Schematics to customize your development process — Angular inDepth
  2. Need to execute the same instructions available in other schematics
    Let’s say with your schematic, you also want to pack some other schematic’s features. For example, you want to create a generic library generator, which works with both Angular and NX, for that, utilizing Angular’s and Nx’s library generator schematics becomes handy.
  3. Decouple set of instructions for better usability
    This can be helpful when you have many tasks going on with a single schematic, but you also want users to run only specific ones. For example, you have a main-schematic, which runs task-1 and task-2. Now to give users the option to run only task-2, you can create one more schematic just to run task-2 and refactor your main-schematic.

Provide instructions to the schematic

  1. Create a rule — A Rule object defines a function that takes a Tree, applies transformations, and returns a new Tree. The main file for a schematic, index.ts, defines a set of rules that implement the schematic's logic.
  2. Add task in context — Each schematic runs in a context, represented by a SchematicContext object. Adding tasks in context is useful when you want to perform operations on tree generated through current execution, like installing packages, performing linting/formatting, etc.
  1. Schematic from our collection
  2. Schematic from external collection
  1. Create a rule to run Schematic from our collection
  2. Create a rule to run Schematic from external collection
  3. Add task in context to run Schematic from our collection
  4. Add task in context to run Schematic from external collection

Create schematics

npm install -g @angular-devkit/schematics-cli
schematics blank --name=run-schematics
cd ./run-schematics
schematics blank --name=child-schematic
// src/child-schematic/index.tsimport { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';export function childSchematic(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
_context.logger.info('Hi from child-schematic');
return tree;
};
}
npm run build
npm link
cd ./path/to/angular/project
npm link run-schematics
ng g run-schematics:run-schematics
# Nothing to be done
ng g run-schematics:child-schematic
# Hi from child-schematic
# Nothing to be done

Create rules

// src/run-schematics/index.tsexport function runSchematics(_options: any): Rule {
return (_tree: Tree, _context: SchematicContext) => {
const rule = schematic("child-schematic", _options);
return rule;
};
}
  • schematicName - The name of the schematic to run
  • options - The options to pass as input to the RuleFactory
ng g run-schematics:run-schematics
# Hi from child-schematic
# Nothing to be done
// src/run-schematics/index.tsexport function runSchematics(_options: any): Rule {
return (_tree: Tree, _context: SchematicContext) => {
const rule1 = schematic("child-schematic", _options);
const rule2 = externalSchematic(
"@schematics/angular",
"component",
_options
);
return chain([rule1, rule2]);
};
}
  • collectionName - The name of the collection that contains the schematic to run
  • Rest 2 are same as schematic function
ng g run-schematics:run-schematics
Hi from child-schematic
? What name would you like to use for the component? hero
CREATE src/app/hero/hero.component.ts (259 bytes)
UPDATE src/app/app.module.ts (738 bytes)

Add tasks in context

  1. NodePackageInstallTask
  2. NodePackageLinkTask
  3. RepositoryInitializerTask
  4. RunSchematicTask
  1. constructor(schemaName: string, options: T) - Runs schematic from the same collection
  2. constructor(collectionName: string, schemaName: string, options: T) - Runs schematic from the external collection
  1. Create lint-schematic - This will perform linting on the newly created sub-app
  2. Create lint-caller-schematic - This will create a sub-app and call lint-schematic through context's task
schematics blank --name=lint-schematic
// src/lint-schematic/index.tsimport { Rule, SchematicContext, Tree } from "@angular-devkit/schematics";
import { execSync } from "child_process";
export function lintSchematic(_options: { name: string }): Rule {
return (_tree: Tree, _context: SchematicContext) => {
_context.logger.info(`Executing: npm run lint -- --fix ${_options.name}`);
execSync("npm run lint -- --fix " + _options.name);
};
}
schematics blank --name=lint-caller-schematic
// src/lint-caller-schematic/index.tsimport {
externalSchematic,
Rule,
SchematicContext,
Tree,
} from "@angular-devkit/schematics";
import { RunSchematicTask } from "@angular-devkit/schematics/tasks";
export function lintCallerSchematic(_options: any): Rule {
return (_tree: Tree, _context: SchematicContext) => {
const rule = externalSchematic(
"@schematics/angular",
"application",
_options
);
_context.addTask(new RunSchematicTask("lint-schematic", _options));return rule;
};
}
ng g run-schematics:lint-caller-schematic --name=sub-app --defaults
_context.addTask(new RunSchematicTask("@schematics/angular", "service", _options));

Summary

rule = schematic(schemaName, options)
context.addTask(new RunSchematicTask(schemaName, options))
rule = externalSchematic(collectionName, schemaName, options)
context.addTask(new RunSchematicTask(collectionName, schemaName, options))

Credits

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store