×

iFour Logo

Introduction to Custom Angular Schematics

Kapil Panchal - December 07, 2020

Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

  • play
  • pause
  • pause
Introduction to Custom Angular Schematics

Preparation


Ensure that you have the following packages installed at a global level on your computer. Note that in a real-life development context, you should have some of these local to your project. But for the having well been stable development environment we will install them globally.

node v12.8.0 npm v.6.10.2 @angular-cli (core y cli) v.10 @schematics/angular @schematics/schematics@0.1000.5 Before getting a start, we need to install @angular-devkit/schematics-cli package to allow us to use the schematics command in our terminal. This command is quite similar to the well-known ng generate but the main benefit is that we can run it anywhere because it is totally independent from the Angular CLI.

This allows us to use the schematics command and specifically the blank schematics to generate a new schematics project where we can start implementing our custom schematics.

Go to the folder where you want to place your schematics to be at and type in your terminal:

		$ schematics blank --name=indepth-dev-schematic
		

As you can already understand, we are basically invoking the schematics function to generate a blank schematic, and we are passing the name of the collection as an option.

If we inspect the generated folder, we can verify it is an npm package, featuring a package.json with needed dependencies, and the node_modules folder.

We will also find a tsconfig.json file and scr folder.

Let we focus on the contents of the src folder

+ src
  • collection.json
+ indepth-dev-schematic
  • Index.ts
  • Index_spec.ts

This file read once by the schematic-cli and the angular-schematic tool, at the time of the running schematics

Any successive schematics in the same package require to be added to the collection

index.ts


import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
// We don't have to export the function by default. we can also have per file more than one rule factory
export function indepthDevSchematic(_options: any): Rule 
{
  return (tree: Tree, _context: SchematicContext) =>
  {
    return tree;
  };
}

As we can see, the function is named camelcase form of the schematic name. This function holds options as arguments and returns a Rule. The Rule is a function that holds the tree and the context and returns another tree.

Most useful things to remember over the entry file index.ts:
  • It can feature a rule factory for certain
  • You do not require to export the function as default

In theory, we could earlier run this schematic through the schematics cli but it will definitely give null output but a console message that ' Nothing to be done'. So, let us make it more interesting and use the create method to create a readme file.

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

// We do not have to export the function as default. You can also have per file more than one rule factory
export function indepthDevSchematic(_options: any): Rule 
{
  return (tree: Tree, _context: SchematicContext) => 
  {

    tree.create('readme.md', '#Mentioned is the Read Me file');
    return tree;
  };
}
		 

Execute custom schematics with the schematic-cli


Now let us head back to the terminal and prompt the schematic execution. We must be inside the schematic folder, at the root level.

Before we can execute it, we require to build our package to trans pile the typescript to JavaScript and compile it. Now we can run.

$ schematics .:indepth-dev-schematic

Because we are at the root level, we do not require to pass the name of the collection. So, it is followed by a colon: and the name of the schematic, in this case, 'indepth-dev-schematic'. In the future, we will add an alias to the schematic to evoke it with a shorter or more user-friendly name.

Schematic didn’t generate anything


Do not distress. This is the desired behavior after schematics run in debug mode, by default. So, if we want to ensure that the schematics update the file system, you require to run them with the –dry-run=false flag.

$ schematics.: indepth-dev-schematic --dry-run=false

Now we should see the readme.md file in your file system.

Passing options as arguments from the CLI


Now we have just hardcoded the values for the file path or name, and the content string. Let us see how to pass it from the CLI to reach a more dynamic output.

In order to do that, let us Modify the RuleFactory like this:

 
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { join } from 'path';
import { capitalize } from '@angular-devkit/core/src/utils/strings';

// We do not have to export the function as default. We can also have per file more than one rule factory

export function indepthDevSchematic(_options: any): Rule
 {
  return (tree: Tree, _context: SchematicContext) => 
  {
    const fullname: string  = _options.fullname;
    const content: string = _options.content;
    const extension: string = _options.extension || '.md';

    tree.create(join(fullname, extension), capitalize(content));
    return tree;
  };
}

Let us create a model now so that we can get rid of ‘any’


Whenever we generate a blank schematic, options are declared as type any. That is because of the generator has no idea that what will be required. We need to solve that by creating a schema model.

Create a file named with schema.ts at the same level of your index.ts and update it like the following:

 
export interface Schema
{
    fullname:string;
    content:string;
    extension?:string;
}

Now we can add the schema type to the option like the following:

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { join } from 'path';
import { capitalize } from '@angular-devkit/core/src/utils/strings';
import { Schema } from './app/schema';

// We do not have to export the function as default. We can also have per file more than one rule factory

export function indepthDevSchematic(_options: Schema): Rule 
{
  return (tree: Tree, _context: SchematicContext) => 
  {
    const fullname: string  = _options.fullname;
    const content: string = _options.content;
    const extension: string = _options.extension || '.md';

    tree.create(join(fullname, extension), capitalize(content));
    return tree;
  };
}

Including a validation schema to our schematic


We can include a validation schema to our schematic by creating a schema.json file at the same level as our entry file. This will serve us to specify defaults for our options, flag them as we needed. Ensure that we are passing the right types and even issuing prompts.

Include the following content to schema.json

{
    "$schema": "http://json-schema.org/schema",
    "id": "indepth-dev-schematics",
    "title": "Demo of Schematics",
    "type": "object",
    "properties": 
	{
      "fullname": 
	  {
        "description": "File name, also same to its path",
        "type": "string",
        "$default": 
		{
          "$source": "argv",
          "index": 0
        }
      },
      "content": 
	  {
        "description": "content of something for that file",
        "type": "string",
        "$default": 
		{
          "$source": "argv",
          "index": 1
        }
      },
      "extension": 
	  {
        "description": "An extension for that file and markdown is to defaults",
        "type": "string",
        "default": ".md"
      }
    },
    "required":
	[
      "name", "content"
    ]
}
 

This schema defines three options as per properties of schema option with id indepth-dev-schematic.fullname and content are argument vectors, at index 0 and 1, by defaults. They are also needed. The third value is the extension and it is not compulsory as user input. It also has it's the default value.

Looking to Hire Angular Developer from Dedicated Team? Your Search ends here.

The schema.json will only implement when referenced from the collection. So, head toward the collection and modify it like the following:

{
    "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
    "schematics": 
	{
      "indepth-dev-schematic": 
	  {
        "description": "A Demo of the blank schematic.",
        "factory": "./indepth-dev-schematic/index#indepthDevSchematic",
        "schema": "./indepth-dev-schematic/schema.json"
      }
    }
}

Input prompts for the custom schematics


Another most important use of the schema is to create prompts to communicate with the user through the CLI. These prompts ensure a better user experience so developers do not have to read tons of documents to realize what input the schematic requirements, in order to run.

There are three types of Prompts which are textual input, either string or number, decision, or yes or no/true or false, and list featuring an enum with subtypes.

Let us update the schema.json to include prompts for the needed options.

{
    "$schema": "http://json-schema.org/schema",
    "id": "indepth-dev-schematics",
    "title": "Demo of Schematics",
    "type": "object",
    "properties":
	{
      "fullname":
	  {
        "description": "File name, also same to its path",
        "type": "string",
        "x-prompt": "What is your file name? (matches path)"
      },
      "content": 
	  {
        "description": "content of something for that file",
        "type": "string",
        "x-prompt": "Please Enter some content for your file"

      },
      "extension": 
	  {
        "description": "An extension for that file and markdown is to defaults",
        "type": "string",
        "default": ".md"
      }
    },
    "required": 
	[
      "name", "content"
    ]
}

Aliases for custom schematics


Before building and run the schematic once again we might maximize it a little bit more by determining a shorter alias, before generating with.: indept-dev-schematic is a bit long error inclined.

To give an alias for it let we go to the collection.json again and modify it like the following:

{
    "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
    "schematics":
	{
      "indepth-dev-schematic": 
	  {
        "description": "A Demo of the blank schematic.",
        "factory": "./indepth-dev-schematic/index#indepthDevSchematic",
        "schema": "./indepth-dev-schematic/schema.json",
        "aliases": ["dive"]
      }
    }
}

We should notice that aliases take an array of strings, so we can define multiple aliases for our schematic.

Now we can execute it from the CLI with

$ schematics .:dive

It should prompt us to pass a full name and content as options. It will realize the default for an extension is .md

Generating the schematic from an Angular app


Unless now, we are running the schematic from the schematics-cli. But that is no fun. We need to run it in an Angular app.

Let us start by linking the package to our current node version executing the following command at the root of our package:

$ npm link

Then generate a new angular app with the Angular CLI and when it gets completed, run the following in the app root folder.

$ npm link indepth-dev-schematic

This creates a symlink to the schematic package so we can execute it Before we run it, let we modify the entry file a bit.

import { Rule,SchematicsException,SchematicContext, Tree } from '@angular-devkit/schematics';
import { join } from 'path';
import { capitalize } from '@angular-devkit/core/src/utils/strings';
import { Schema } from './app/schema';

// We do not have to export the function as default. We can also have per file more than one rule factory

export function indepthDevSchematic(_options: Schema): Rule 
{
  return (tree: Tree, _context: SchematicContext) => 
  {
    const fullname: string  = _options.fullname;
    const content: string = _options.content;
    const extension: string = _options.extension || '.md';
    const path = join(name, extension);
    const angularConfig = 'angular.json';
    // Let we ensure that we are in an angular workspace
    if (!tree.exists(angularConfig))
	{
      throw new SchematicsException('It is not an Angular worksapce. Please Try again in an Angular project.');
    } else {
      if (!tree.exists(path))
	  {
        tree.create(path, capitalize(content));
      } else 
	  {
        throw new SchematicsException('This name of file is already exists! Please try a different or new name');
      }
    }
    return tree;
  };
}

Using these changes, we ensure that we are executing the schematic in an angular workspace and that the file doesn't already exist.

Now after rebuilding we can ultimately go to the app and run

$ ng generate indepth-dev-schematic:dive

Conclusion


In this blog, we have discussed the angular custom schematics with a proper example of creating a custom schematic with useful guidance.

Introduction to Custom Angular Schematics Schematics are very useful. They provide us to achieve more in a shorter amount of time. But most significantly used, we can think less about routine stuff which leaves our limited attention span to focus on solving real changes. Table of Content 1. Preparation 2. index.ts 3. Execute custom schematics with the schematic-cli 4. Schematic didn’t generate anything 5. Passing options as arguments from the CLI 6. Let us create a model now so that we can get rid of ‘any’ 7. Including a validation schema to our schematic 8. Input prompts for the custom schematics 9. Aliases for custom schematics 10. Generating the schematic from an Angular app 11. Conclusion Preparation Ensure that you have the following packages installed at a global level on your computer. Note that in a real-life development context, you should have some of these local to your project. But for the having well been stable development environment we will install them globally. node v12.8.0 npm v.6.10.2 @angular-cli (core y cli) v.10 @schematics/angular @schematics/schematics@0.1000.5 Before getting a start, we need to install @angular-devkit/schematics-cli package to allow us to use the schematics command in our terminal. This command is quite similar to the well-known ng generate but the main benefit is that we can run it anywhere because it is totally independent from the Angular CLI. This allows us to use the schematics command and specifically the blank schematics to generate a new schematics project where we can start implementing our custom schematics. Go to the folder where you want to place your schematics to be at and type in your terminal: $ schematics blank --name=indepth-dev-schematic As you can already understand, we are basically invoking the schematics function to generate a blank schematic, and we are passing the name of the collection as an option. If we inspect the generated folder, we can verify it is an npm package, featuring a package.json with needed dependencies, and the node_modules folder. We will also find a tsconfig.json file and scr folder. Let we focus on the contents of the src folder + src collection.json + indepth-dev-schematic Index.ts Index_spec.ts This file read once by the schematic-cli and the angular-schematic tool, at the time of the running schematics Any successive schematics in the same package require to be added to the collection index.ts import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; // We don't have to export the function by default. we can also have per file more than one rule factory export function indepthDevSchematic(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { return tree; }; } As we can see, the function is named camelcase form of the schematic name. This function holds options as arguments and returns a Rule. The Rule is a function that holds the tree and the context and returns another tree. Read More: Setting Up Angular In The System Most useful things to remember over the entry file index.ts: It can feature a rule factory for certain You do not require to export the function as default In theory, we could earlier run this schematic through the schematics cli but it will definitely give null output but a console message that ' Nothing to be done'. So, let us make it more interesting and use the create method to create a readme file. import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; // We do not have to export the function as default. You can also have per file more than one rule factory export function indepthDevSchematic(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { tree.create('readme.md', '#Mentioned is the Read Me file'); return tree; }; } Execute custom schematics with the schematic-cli Now let us head back to the terminal and prompt the schematic execution. We must be inside the schematic folder, at the root level. Before we can execute it, we require to build our package to trans pile the typescript to JavaScript and compile it. Now we can run. $ schematics .:indepth-dev-schematic Because we are at the root level, we do not require to pass the name of the collection. So, it is followed by a colon: and the name of the schematic, in this case, 'indepth-dev-schematic'. In the future, we will add an alias to the schematic to evoke it with a shorter or more user-friendly name. Schematic didn’t generate anything Do not distress. This is the desired behavior after schematics run in debug mode, by default. So, if we want to ensure that the schematics update the file system, you require to run them with the –dry-run=false flag. $ schematics.: indepth-dev-schematic --dry-run=false Now we should see the readme.md file in your file system. Passing options as arguments from the CLI Now we have just hardcoded the values for the file path or name, and the content string. Let us see how to pass it from the CLI to reach a more dynamic output. In order to do that, let us Modify the RuleFactory like this:   import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; import { join } from 'path'; import { capitalize } from '@angular-devkit/core/src/utils/strings'; // We do not have to export the function as default. We can also have per file more than one rule factory export function indepthDevSchematic(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { const fullname: string = _options.fullname; const content: string = _options.content; const extension: string = _options.extension || '.md'; tree.create(join(fullname, extension), capitalize(content)); return tree; }; } Let us create a model now so that we can get rid of ‘any’ Whenever we generate a blank schematic, options are declared as type any. That is because of the generator has no idea that what will be required. We need to solve that by creating a schema model. Create a file named with schema.ts at the same level of your index.ts and update it like the following:   export interface Schema { fullname:string; content:string; extension?:string; } Now we can add the schema type to the option like the following: import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; import { join } from 'path'; import { capitalize } from '@angular-devkit/core/src/utils/strings'; import { Schema } from './app/schema'; // We do not have to export the function as default. We can also have per file more than one rule factory export function indepthDevSchematic(_options: Schema): Rule { return (tree: Tree, _context: SchematicContext) => { const fullname: string = _options.fullname; const content: string = _options.content; const extension: string = _options.extension || '.md'; tree.create(join(fullname, extension), capitalize(content)); return tree; }; } Including a validation schema to our schematic We can include a validation schema to our schematic by creating a schema.json file at the same level as our entry file. This will serve us to specify defaults for our options, flag them as we needed. Ensure that we are passing the right types and even issuing prompts. Include the following content to schema.json { "$schema": "http://json-schema.org/schema", "id": "indepth-dev-schematics", "title": "Demo of Schematics", "type": "object", "properties": { "fullname": { "description": "File name, also same to its path", "type": "string", "$default": { "$source": "argv", "index": 0 } }, "content": { "description": "content of something for that file", "type": "string", "$default": { "$source": "argv", "index": 1 } }, "extension": { "description": "An extension for that file and markdown is to defaults", "type": "string", "default": ".md" } }, "required": [ "name", "content" ] } This schema defines three options as per properties of schema option with id indepth-dev-schematic.fullname and content are argument vectors, at index 0 and 1, by defaults. They are also needed. The third value is the extension and it is not compulsory as user input. It also has it's the default value. Looking to Hire Angular Developer from Dedicated Team? Your Search ends here. See here The schema.json will only implement when referenced from the collection. So, head toward the collection and modify it like the following: { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "indepth-dev-schematic": { "description": "A Demo of the blank schematic.", "factory": "./indepth-dev-schematic/index#indepthDevSchematic", "schema": "./indepth-dev-schematic/schema.json" } } } Input prompts for the custom schematics Another most important use of the schema is to create prompts to communicate with the user through the CLI. These prompts ensure a better user experience so developers do not have to read tons of documents to realize what input the schematic requirements, in order to run. There are three types of Prompts which are textual input, either string or number, decision, or yes or no/true or false, and list featuring an enum with subtypes. Let us update the schema.json to include prompts for the needed options. { "$schema": "http://json-schema.org/schema", "id": "indepth-dev-schematics", "title": "Demo of Schematics", "type": "object", "properties": { "fullname": { "description": "File name, also same to its path", "type": "string", "x-prompt": "What is your file name? (matches path)" }, "content": { "description": "content of something for that file", "type": "string", "x-prompt": "Please Enter some content for your file" }, "extension": { "description": "An extension for that file and markdown is to defaults", "type": "string", "default": ".md" } }, "required": [ "name", "content" ] } Aliases for custom schematics Before building and run the schematic once again we might maximize it a little bit more by determining a shorter alias, before generating with.: indept-dev-schematic is a bit long error inclined. To give an alias for it let we go to the collection.json again and modify it like the following: { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "indepth-dev-schematic": { "description": "A Demo of the blank schematic.", "factory": "./indepth-dev-schematic/index#indepthDevSchematic", "schema": "./indepth-dev-schematic/schema.json", "aliases": ["dive"] } } } We should notice that aliases take an array of strings, so we can define multiple aliases for our schematic. Now we can execute it from the CLI with $ schematics .:dive It should prompt us to pass a full name and content as options. It will realize the default for an extension is .md Generating the schematic from an Angular app Unless now, we are running the schematic from the schematics-cli. But that is no fun. We need to run it in an Angular app. Let us start by linking the package to our current node version executing the following command at the root of our package: $ npm link Then generate a new angular app with the Angular CLI and when it gets completed, run the following in the app root folder. $ npm link indepth-dev-schematic This creates a symlink to the schematic package so we can execute it Before we run it, let we modify the entry file a bit. import { Rule,SchematicsException,SchematicContext, Tree } from '@angular-devkit/schematics'; import { join } from 'path'; import { capitalize } from '@angular-devkit/core/src/utils/strings'; import { Schema } from './app/schema'; // We do not have to export the function as default. We can also have per file more than one rule factory export function indepthDevSchematic(_options: Schema): Rule { return (tree: Tree, _context: SchematicContext) => { const fullname: string = _options.fullname; const content: string = _options.content; const extension: string = _options.extension || '.md'; const path = join(name, extension); const angularConfig = 'angular.json'; // Let we ensure that we are in an angular workspace if (!tree.exists(angularConfig)) { throw new SchematicsException('It is not an Angular worksapce. Please Try again in an Angular project.'); } else { if (!tree.exists(path)) { tree.create(path, capitalize(content)); } else { throw new SchematicsException('This name of file is already exists! Please try a different or new name'); } } return tree; }; } Using these changes, we ensure that we are executing the schematic in an angular workspace and that the file doesn't already exist. Now after rebuilding we can ultimately go to the app and run $ ng generate indepth-dev-schematic:dive Conclusion In this blog, we have discussed the angular custom schematics with a proper example of creating a custom schematic with useful guidance.

Build Your Agile Team

Enter your e-mail address Please enter valid e-mail

Categories

Ensure your sustainable growth with our team

Talk to our experts
Sustainable
Sustainable
 

Blog Our insights

Power Apps vs Power Automate: When to Use What?
Power Apps vs Power Automate: When to Use What?

I often see people asking questions like “Is Power App the same as Power Automate?”. “Are they interchangeable or have their own purpose?”. We first need to clear up this confusion...

Azure DevOps Pipeline Deployment for Competitive Business: The Winning Formula
Azure DevOps Pipeline Deployment for Competitive Business: The Winning Formula

We always hear about how important it is to be competitive and stand out in the market. But as an entrepreneur, how would you truly set your business apart? Is there any way to do...

React 18 Vs React 19: Key Differences To Know For 2024
React 18 Vs React 19: Key Differences To Know For 2024

Ever wondered how a simple technology can spark a revolution in the IT business? Just look at React.js - a leading Front-end JS library released in 2013, has made it possible. Praised for its seamless features, React.js has altered the way of bespoke app development with its latest versions released periodically. React.js is known for building interactive user interfaces and has been evolving rapidly to meet the demands of modern web development. Thus, businesses lean to hire dedicated React.js developers for their projects. React.js 19 is the latest version released and people are loving its amazing features impelling them for its adoption.