Angular Schematics from 0 to publishing your own library (II)

In this second post, we will explore some of the most useful methods of the Schematics API. It definitely offers a lot of functions and utilities, to transform our Angular workspace.

Angular Schematics from 0 to publishing your own library (II)

In this second post, we will explore some of the most useful methods of the Schematics API. It definitely offers a lot of functions and utilities, to transform our Angular workspace.

Methods to read the file system

These methods are executed on the base, and allows you to traverse it and get useful meta data and other information about the current state.

getDir()

This method allows you to get information about the contents of a specific folder in your base.

//  ...node_modules/@angular-devkit/schematics/src/tree/interface.d.ts

const hasEntries = tree.getDir(normalize(`${staticPath}${_options.path}`))

get()

This method allows you to get a single file in the source.

visit()

This method is useful to traverse the workspace from a path and below. We can then get concrete information about each file visited.

 tree.getDir('/').visit(filePath => {
      if (filePath.includes('node_modules')) {
        return;
      }
 )}

read()

Read is meant to be used to read and extract information of a single file resource. It returns a Buffer.

 const tsConfigBuffer = tree.read(filePath);

      if (!tsConfigBuffer) {
        return;
      }

apply()

This method allows you to apply multiple rules to a source, and it returns the transformed tree.

url()

This method allows you accessing the base existing in the file system, that wants to be replicated, relative to the root of the schematic that implements it

applyTemplate() | template()

These methods take a rule or several, as arguments, and the path to the files to replicate (also accessed via another method, the url())

If you want to implement string utilities to your templates, you'll do it via these methods.

move()

This method allows you to move a file or tree to a new location

noop()

This method allows you to explicitly describe no operations for certain conditions met.

const source = apply(url('./files'), [
      template({
        ...strings,
        ..._options,
      }),
      move(path)
    ]);

String Utilities

String utilities are helpful to normalize definitions and to enforce conventions. For example, when we work in large teams, (or even when we work on our own! ) we want standards to be followed and patterns to be enforced to

  • name artifacts
  • name variables
  • follow styleguide best practices, etc

These utility methods are available in the templates directly, without having to export anything else, additionally. Although we do have to import them to Schematics entry point or rule factory

When describing structures, we can even chain unlimited methods an unlimited amount of times. We only need to follow this syntax

__name@dasherize__

Where __ (two underscores) act as delimiter by default and @ indicates the concatenation of methods.

dasherize()

This method takes a string value and returns the same value with dashes and lowercase. (kebab-case)

For example, should we pass `InDepthDev`, it would return `in-depth-dev`

classify()

This method takes a string value and returns the same value with the first character in uppercase, and the rest in lowercase. (PascalCase)

For example, should we pass `in depth dev`, it would return `InDepthDev`

camelize()

This method takes a string value and returns the same value with the first character in lowercase, the first successive characters in uppercase, and the rest in lowercase. (camelCase)

For example, should we pass `in depth dev`, it would return `inDepthDev`

decamelize()

This method takes a string value and returns the same value replacing all spaces or caps as delimiters, with dashes

For example, should we pass `inDepthDev`, it would return `in-depth-dev`

undescore()

This method takes a string value and returns the same value replacing all spaces or caps as delimiters, with underscores

For example, should we pass `in Depth Dev`, it would return `in_depth_dev`

Chaining or combining schematics

The following methods exists in order to allow the combination and extension of independent schematics.

schematic()

This method allows you to pass a schematic collection, a schematic alias or entry rule name, as it appears in the collection, and the options, that are part of the same collection, to be chained with the current schematic

externalSchematic()

This method allows you to pass a schematic collection, a schematic alias or entry rule name, as it appears in the collection, and the options, that are part of an external collection, to be chained with the current schematic

chain()

This method allows you to combine multiple schematics rules, to obtain a single concatenated rule, and be able to synchronously execute several operations with different concerns

Merging strategies

This methods are meant to decouple the source into branches in the staging area, where transformations can be applied.

branchAndMerge()

This method allows you to branch out from the source, apply transformations and then merge back to it.

mergeWith()

This method allows you to merge trees, after transformations are applied, and usually back to the physical source

Template syntax

Schematics allows us to implement template syntax in a very flexible way, for example:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class <%= classify(name) %>Service {
  constructor(private http: HttpClient) { }
}

This code is part of the implementation of the service schematic, externalSchematic of the CLI. In this way, the service gets the name passed to the schematic as option

Another very interesting use of the template syntax is directly in templates, which allows you to access string utilities but also to any other custom function defined in the schematic, as seen in the example

const source = apply(url('./files'), [
      forEach((file: FileEntry) => {
        let dir = dirname(file.path);
        let pathName = basename(dir);
        _options.folderName = pathName;
        _context.logger.info(`Estamos leyendo en árbol virtual -> ${pathName}`);
        return file;
      }),
      template({
        ...strings,
        ..._options,
        addProjectInfo
      }),
      move(path)
    ]);

    function addProjectInfo(): string {
      return `This is the readme file for project: ${projectName}. You can find more info about Angular on [this link](https://www.angular.io)`
    }

Logging

A very important aspect at the time of developing developer oriented tools, and at the time of executing CLI commands as a user, is the ability of obtaining real time information about the executing process.

The schematics context allows us to access the LoggerApi, an implementation you can see here `/node_modules/@angular-devkit/core/src/logger/logger.d.ts`

Exceptions

A very best practice is to have a good errors and exceptions catching strategy. Schematics offers use the SchematicsExceptionsutility that implements BaseExceptionfrom Angular core. You can explore it here /node_modules/@angular-devkit/core/src/exception/exception.d.ts

Up to now we explored the API only in theory! Meet me in the next post to explore the schematics the Angular CLI comes equipped with, to generate artifacts, add and install libraries, and update dependencies.

We will also write our first schematics together!