Exploring the File System

These methods operate on the base tree, giving you the ability to navigate it and extract metadata or insights about its current state.

getDir()

Use this method to retrieve details about the contents of a given directory within your base tree.

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

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

get()

This method retrieves a specific file from the source tree.

visit()

This method lets you walk through the workspace starting from a specified path and moving downward. For each file encountered along the way, you can gather specific information.

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

read()

The read method is intended for accessing and extracting data from a single file. Its return value is a Buffer.

 const tsConfigBuffer = tree.read(filePath);

      if (!tsConfigBuffer) {
        return;
      }

apply()

This method applies a set of rules to a source and yields the resulting transformed tree.

url()

This method grants access to a base tree stored in the file system that you wish to replicate, with paths resolved relative to the root of the schematic using it.

applyTemplate() | template()

These methods accept one or multiple rules along with the path to the files you intend to clone (the path is also obtained via the url() method).

If you need to incorporate string utilities into your templates, this is where you hook them in.

move()

This method relocates a file or an entire tree to a different destination.

noop()

This method explicitly signifies that no operations should occur under specific conditions.

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

String Helpers

String utilities are valuable for standardizing names and reinforcing conventions. Whether you are working in a large team or solo, it is beneficial to enforce consistent standards for:

  • naming components and files
  • naming variables
  • adhering to style guide recommendations

These helpers are accessible directly within templates without any extra exports. However, you do need to import them in your schematic's entry point or rule factory.

When defining structures, you can chain as many methods as needed without limits. Just follow this pattern:

__name@dasherize__

Here, the double underscores (__) serve as the default delimiter, while the @ symbol denotes where methods are concatenated.

dasherize()

This method accepts a string and converts it to lowercase with dashes separating words (kebab-case).

For instance, passing `InDepthDev` results in `in-depth-dev`.

classify()

This method takes a string and capitalizes the first letter while lowering the rest (PascalCase).

For example, passing `in depth dev` yields `InDepthDev`.

camelize()

This method transforms a string so the first character is lowercase, subsequent word starts are uppercase, and the remainder stays lowercase (camelCase).

Passing `in depth dev` produces `inDepthDev`.

decamelize()

This method converts a string by replacing spaces or uppercase letters with dashes.

For example, `inDepthDev` becomes `in-depth-dev`.

underscore()

This method takes a string and swaps spaces or uppercase boundaries with underscores.

Passing `in Depth Dev` returns `in_depth_dev`.

Combining Schematics

The following methods exist to support combining and extending standalone schematics.

schematic()

This method lets you specify a schematic collection, a schematic alias or entry rule name as defined in the collection, along with its options, to chain it with the current one.

externalSchematic()

This method is similar, but the referenced collection is external. You pass the collection name, the schematic alias or entry rule, and the options to chain it with your current schematic.

chain()

This method merges multiple schematic rules into one combined rule, allowing sequential execution of operations that address different concerns.

Merge Approaches

These methods are designed to separate the source into branches where transformations can be carried out independently before being reintegrated.

branchAndMerge()

This method creates a branch from the source, applies modifications, and then merges the results back.

mergeWith()

This method combines trees after transformations have been applied, typically merging back into the physical source.

Template Expressions

Schematics supports highly flexible template syntax. 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 snippet comes from the service schematic, which is part of the CLI's external schematics. It allows the service name to be supplied via the schematic's options.

Another compelling application is using template syntax in file templates themselves. This approach gives you access to string utilities as well as any custom functions defined within the schematic, as shown below:

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

When building developer-focused tools—or when running CLI commands yourself—it is crucial to receive real-time feedback about ongoing operations.

The schematic context provides access to the LoggerApi. You can find its implementation at /node_modules/@angular-devkit/core/src/logger/logger.d.ts.

Handling Exceptions

Implementing a solid error-handling strategy is a best practice. Schematics provides the SchematicsException utility, which extends BaseException from Angular core. You can look at its definition at /node_modules/@angular-devkit/core/src/exception/exception.d.ts.

So far, we have only covered the API in theory. Join me in the next installment, where we will examine the schematics bundled with the Angular CLI for generating artifacts, adding and installing libraries, and updating dependencies.

We will also build our first schematic together!