Guide to custom Angular Schematics

When creating a new Angular project, how many times have you thought of copying files and folders for an initial setup of your Angular project? I’ve done it a million times (okay, not million, but you get the point, right?).

Rationale behind using Schematics

What the heck is Schematics, after all?

A schematic is a template-based code generator that supports complex logic. It is a set of instructions for transforming a software project by generating or modifying code. Schematics are packaged into collections and installed with npm.

Basically, we all know about ng new project-name command, right? This is a schematics which helps us in creating our Angular project, which adds all the files required to build an Angular project. Now, suppose you want to create your own command so that helps you add all the folders and dependencies that are required for you in every new project you create.

The schematic collection can be a powerful tool for creating, modifying, and maintaining any software project, but is particularly useful for customizing Angular projects to suit the particular needs of your own organization.

Creating a new Schematic

npm install -g @angular-devkit/schematics-cli

Now, we can use it to create a new schematic project, just like we use the Angular CLI to create a new app.

schematics blank --name=angular-schematics

The blank parameter is used to create a project with minimal files.

Main files that we need to know about:-

  • collection.json is like an index of the schematics in this project. It links the name of each schematic with the code that runs it.
  • sample-schematics-project/index.ts is the code run by our schematic.

Because everything is in TypeScript, we need to build it before we run it. We can do that by running `npm run build`. Running the build script manually can get quite tedious. Instead, we can get the TypeScript compiler to watch for changes and build automatically, by adding a watch script to package.json

"scripts": {
"build": "tsc -p tsconfig.json",
"watch": "tsc -p tsconfig.json --watch",

}

Getting User Input

We’ve to tell schema.json that our schematic is expecting an input, for example in the below code we’ve added name as one of the properties and it’s data type as string.

Allowed Data Types :-

  1. String
  2. Number
  3. Boolean
  4. enum
Asking for user input

The x-prompt value is the prompt that the schematic will use to ask the user for a value, if they don’t supply one. The default object allows you to provide a default value. In this case, the default value will be the first argument the user passes in on the command line.

Calling “ng new” Schematic

Calling ng new schematic

The first thing we want to do is call the ng new schematic, passing in our name option, and a few other default options. To do that, we’re finally going to write some TypeScript!

We have a function, angularSchematic, which is going to get called when the schematic runs. It’s going to get passed in _options, which we can use to get the name. It’s going to return a function, which takes a Tree and a SchematicContext, and returns a new Tree. A function like this is referred to as a Rule. A function which returns a Rule is a rule factory.

A Tree is a fundamental concept in Schematics. It refers to the schematic’s internal representation of the file system. For those familiar with React, it’s kind of like a virtual DOM, but for the file system. When we make changes, they’re applied to the Tree, rather than the actual files. Once we’ve finished with all our changes, the Tree is written to the file system (or not, if we’re in debug mode).

Calling a custom Schematic and creating custom rules

Ok, once we’ve got our files, we need a way to add them to the Tree. To do this, we can use another Rule factory, called mergeWith. To use mergeWith, we need to pass in a templateSource and a MergeStrategy.

'./files' is the location of the files we want to include, _options is the options passed into our schematic (which will be used by the placeholders), and strings is a collection of utility functions (like camelize and classify) provided by schematics. You can import string from @angular-devkit/core.

Once we’ve got our templateSource, we can pass it into the mergeWith factory along with a MergeStrategy.

const merged = mergeWith(templateSource, MergeStrategy.Overwrite)

MergeStrategy.Overwrite means that if the Tree contains a file that’s also in our templateSource, then use the one in our templateSource.

Ok, so now we have two rules — one returned by mergeWith, and one from externalSchematic. We need to apply both of them to our tree, but we can only return a single rule. So we need some way to combine the two.

Chain takes in a list of Rule factories, and returns a factory that combines them all.

Update package.json

To do this, we’re going to write our own Rule factory, called updatePackageJson. This factory will need to be passed in the name of the project, and will return a Rule. The next thing we need to do is read in the current package.json file. We can use tree.read(path) to fetch the contents of the file as a buffer, then, because we’re dealing with a JSON file, we can use JSON.parse() to parse it.

Read package.json

Now that we’ve parsed our package.json file, we can manipulate it just like any other object.

Updating package.json

Run the Schematic

schematics ./new-project/src/collection.json:angular-schematic new-app

This will create a new Angular application with name new-app.

Publish our Schematic

Web Developer, loves to write about UI technologies