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
Be it a personal project or an enterprise project, I always wish I could use my own template project to get a great kick off. I wanted to have my core module, graphQL module, all the Apollo dependencies automatically installed, ESLint, prettier and husky hooks configured and etc etc.. every time I started my project. BUTT, I always had to copy paste the exact folders and files every time. And I recently stumbled upon the concept of Schematics and it has made the start of the new project 2x faster.
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
To create a new schematics project we’re going to use an NPM package called
schematics. You’ll need to install it globally, just like the Angular CLI.
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
"build": "tsc -p tsconfig.json",
"watch": "tsc -p tsconfig.json --watch",
Getting User Input
We’ve talked about how to create a dummy schematic, which doesn’t really do anything, let’s see how can e get user inputs and how can we create a dummy project.
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 :-
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
Now, let’s talk about how can we create an Angular project using a schematic. We’re aware of
ng new command, we’ll see how can we make our schematic call an external 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.
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
Now let’s suppose we want that whenever we use this schematic we add certain files. For that, firstly we need to put all the files and folders in our project and you can also add placeholders inside files, using
<%= optionName %>.
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
'./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
Once we’ve got our
templateSource, we can pass it into the
mergeWith factory along with a
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
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.
Now if we want to add a few dependencies to our project upfront we can easily do that with our schematics.
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.
Now that we’ve parsed our package.json file, we can manipulate it just like any other object.
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
Now, once we have created our schematic we can publish it using
npm publish and everyone can start using it.