Serving Industries Worldwide

Innovative Ways - Satisfied Clientele

Introduction to Custom Angular Schematics


Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

 
 

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

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
  1. collection.json
+ indepth-dev-schematic
  1. Index.ts
  2. 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:
  1. It can feature a rule factory for certain
  2. 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 dedicated Angular Developer ?

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

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

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
  1. collection.json
+ indepth-dev-schematic
  1. Index.ts
  2. 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:
  1. It can feature a rule factory for certain
  2. 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 dedicated Angular Developer ?

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.