Total Guide To Custom Angular Schematics

Original 📷 by Sveta Fedarava

Schematics are great! They enable us to achieve more in shorter amount of time! But most importantly, we can think less about mundane stuff which leaves our limited attention span to focus on solving real challenges!

Check out video from Angular Meetup Zurich with live coding of custom schematics!

☕☕☕☕☕ 20 minutes read

⚠️ This is a pretty long article which gets into every detail of implementing custom Angular schematics…

This article will teach you how to create tailor made schematics specific to the needs of your project or organization! It will empower you and your colleagues to become much more productive!

What are we going to learn?

  1. Create custom schematics project workspace
  2. Concepts that describe schematics implementation (Factory, Rule, Tree…)
  3. Build schematics (and setup watch build for convenience)
  4. Run schematics (package vs relative path, debug mode, …)
  5. Implement simple schematics (generate a file, …)
  6. Parametrize schematics with schema and options
  7. Use advanced schematics templates (and string helper functions)
  8. Integrate custom schematics in Angular CLI worksapce
  9. Test custom schematics
  10. Build and publish custom schematics package
  11. Implement schematics as a part of Angular library project
  12. Add ng-add support
  13. Add ng-update support


Before we start, we have to install @angular-devkit/schematics-cli package to be able to to use schematics command in our terminal. This command is similar to well-known ng generate but the main advantage is that we can run it anywhere because it’s completely independent from the Angular CLI.

This enables us to use schematics command and especially the blank schematics to generate new schematics project where we can start implementing our custom schematics.

1. Create project

Let’s generate new schematics project using schematics blank hello. What we will get is a folder with the following structure.

Files generated by the “schematics blank hello” command

Let’s have a closer look on what happened here and what are the most important files and content.

First of all, the collection.json file is the main definition file of the whole schematics library and contains definitions of all schematics available in that library.

Our initial collection.json file will look like this…

Content of our generated collection.json file

We can see that our library contains one schematic with the name hello which points to the ./hello/index file and more specially to the #hello function in that file.

Besides that we can specify also description and some other fields which we will add later. The description will be displayed when we use our hello schematic in Angular CLI workspace together with the --help flag but more on that later 😉.

Now, let’s have a look into the ./hello/index.ts file.

Initially generated Angular Schematics factory stub with additional comments for clarity

2. Understand schematics concepts

Our schematic consists of multiple parts playing nicely together. Let’s learn more about these individual parts and their relationship!

The schematic factory

First of all, our file contains the hello function which is a factory function which returns us a Rule.

That way we can parametrize our schematic rule (returned by the factory) so that it behaves accordingly.

The Rule

The second main component of the schematic implementation is the rule itself. Rule is called with a Tree and a SchematicContext. Once called, rule is supposed to make adjustments to the tree and return it back for the further processing.

The Tree

We learned that the rule receives tree and makes changes to that tree but what does the tree represent?

Schematics are all about code generation and changing of existing files!

The tree is then virtual representation of every file in the workspace. Using a virtual tree instead of manipulating files directly comes with a couple of big advantages.

  • we only commit changes to the tree if EVERY schematics ran successfully
  • we can preview changes without actually making them ( with --dry-run flag)
  • the whole process is faster as the I/O operations ( writing files to disk ) happens only at the end of the processing

Let’s summarize what we learned until now…

The schematic consist of a main file which exports a factory function. This function is called with the schematics options. The factory returns us a rule which is called with the virtual representation of the file system, aka the tree and the schematics context.

3. Build schematics

Our schematics project is implemented with Typescript but we run it as a standard node application. Because of that, running our hello schematic would result in error if we didn’t compile all .ts files beforehand. To do that we can simply run npm run build.

Add following line to the package.json file and run it using npm run build:watch

4. Run schematic

Cool we have our generated hello schematic so let’s try to run it. Previously we have been running schematics inside of the Angular CLI workspaces using ng generate but our schematics project doesn’t contain Angular CLI…

So what can we do?

Luckily, the schematics command allows us to specify package containing schematics. The command looks like this schematics <package-name>:<schematic-name> [...options].

The package name can be for example @schematics/angular and the schematic name component. The schematics command will by default try to find package inside of the node_modules folder in the directory where it was executed.

Our hello schematic was not yet packaged though…

In this case we can use relative path instead of the package name and we will get command which looks like schematics .:hello

Executing schematics from the local project using relative path (the “.” stands for current folder)

Running schematics with relative path outside of the current folder

What if we wanted to run our hello schematic outside of the folder where it was implemented? It is possible but the relative path has to point exactly to the collection.json file instead of just the schematics project folder.

Running schematics using relative path from some other folder (remember to point exactly to the collection.json file)

Follow me on Twitter to get notified about the newest Angular blog posts and interesting frontend stuff!🐤

5. Implement simple schematic

OK, we have a schematic implementation stub and we know how to build it and run it!

Now is the time to start creating something useful!

Let’s adjust our hello schematic rule so that it create a hello.js file with a console.log('Hello World!'); content. To do that we have to use .create() method which is available on the tree object.

Let’s run this adjusted schematic using schematics .:hello and see what happens!

We ran our hello schematic which created hello.js file but we can’t see it when we list the folder content, why?

The reason for this is that we specified our schematics package as a relative path (the .) and in that case the schematics are executed in the debug mode. The debug mode results in the same behavior as if running schematics with the --dry-run flag so no changes get committed to the file system.

We have two options to fix this situation. We can either run our command with --debug=false or --dry-run=false flag.

Our hello.js file gets generated and we can run it using node hello.js
Try out @angular-extensions/model library! Check out docs & Github

6. Parametrize schematics with schema and options

Nice, our hello schematic is now generating hello.js file which logs Hello World! string into console when called. Let’s make this more useful by allowing us to define who we want to greet!

Our schematic is called with the _options object which will contain all the flags that we pass when executing our schematic in the command line. For our purposes we can execute schematics .:hello --name=Tomas --debug=false

Retrieve name from the _options object and use it to parametrize our Hello string…

This will work and schematics will by default pass every specified flag to the _options object but we can do even better!

What we can do is to create Schema which describes what kind of options we can pass to our schematic. That way schematics will perform validation of the passed options and give hints to what we have to do to get desired results!

First of all, we have to create schema.json file with the following content inside of our ./src/hello folder.

Example of a minimal schema.json file which specifies name positional option

The file is pretty self-explanatory. The properties object contains definitions of all supported options. Options can have various types including string, boolean or even enum which validates passed value against the list of valid values.

Now we have to reference our newly generated schema.json file in the collection.json file to use it to validate command line option flags.

Add reference to schema.json file in the collection.json file

Now we can create also schema.d.ts which will provide us with type checking and code completion in our schematic implementation.

And use it in our hello schematics…

Enhancing options with prompts

Specifying options enhances developer experience because we can provide them with helpful, well described error messages which will help them understand what they have to do to make the schematic work.

We can do even better by adding prompts!

Prompts are the way to provide schematic options without the need to know about them before hand. To use them we have to add x-prompt property to our schema.json file in the definition of the name option.

And this is how it will look like once executed with schematics .:hello --debug=false (note, we didn’t provide --name or positional name argument).

7. Use schematic templates

Until now we only used basic Javascript template string to create the content of our file. This works but is very basic and doesn’t provide us with any support in terms of where the file should be created. Luckily, Angular Schematics templates are here for the rescue!

To use templates we can start by creating ./files/ folder in our ./hello/ folder.

Let’s add one more nested folder inside of the ./files/ folder with the name hello-__name@dasherize__. This looks pretty funny on the first sight so let’s see what is going on.

This means that if we provided name like AwesomeWrap the resulting folder name would be hello-awesome-wrap. Yummy 🥙 !

Now we will create file with similar name inside of our last folder with the name hello-__name@dasherize__.ts. This works the same way as with the folder described previously.

Now, for the content of our newly created file we have to add this…

The Angular Schematics template syntax consists of <%= opening and %> closing tags to print value of some variable. It also supports function calls like dasherize or classify to adjust variable value inside of the template.

Example of how to use helper function in the Angular Schematics templates

Our template is ready but we still have to wire things up inside of the schematics rule.

Adjustment we made to our original Angular Schematics rule

With these adjustments in place we can try it by running schematics .:hello 'Tomas Trajan' --debug=false

More on the template helpers

The template helpers like dasherize or classify are available because we’re spreading strings object into the options object we’re passing into the template…

The thing is we could actually pass in any function. Let’s say we would like to add exclamation to the name. What we can do is to create addExclamation function and pass it in too!

Any function can be passed and used in the template which is great for abstracting away reusable parts of template creation logic!

Useful schematics

Cool now we have a simple working schematic which uses templates!

The hello schematic in itself is not that exciting but the possibilities are endless!

Imagine we wanted to generated something like a CRUD resource service for our Angular application. The schematic itself could be exactly the same, the only difference would be more complex template…

Note we are using dasherize, classify and camelize helper functions in this template to accommodate for all of our needs

Such a template would generate following file if called with the name 'product code'

File generated for previous template and name ‘product code’

Conditional templates

Let’s say that some of our CrudResourceService instances should give data back as it is received from backend but some have to transform it to better suit our frontend needs.

In that case we could add new boolean option to our schema.json (and .d.ts ) files with the name transform.

Add transform option to schema.json file
Add transform option to schema.d.ts file

Then we could use that variable also in the template to implement if / else conditional parts like this…

We can use if / else (and even loops) in our Angular Schematics templates!

Please notice that the if / else blocks are delimited using <% instead of <%= which is used to print value of a variable.

Great! We’re generating something useful. Now is the time to put it all together and integrate it with Angular CLI ng generate command!

Additional template language features

Angular Schematics templates seem to support every JavaScript language feature. Let’s say we would also like to pass an array of frameworks to our template.

In that case we can use standard for loop to write these items into the template in any form, be it html or functions…

How to use for loop inside Angular Schematics templates

8. Integrate custom schematics with Angular CLI

Angular CLI comes with a bit specific environment which needs ti be handled so that our custom schematics integrate seamlessly.

Every time we run ng generate component we’re generating it in some project. The angular.json file contains definitions of all workspace projects together with the defaultProject property. It will determine the location of our component. We can override it by specifying --project some-app flag in the ng generate command explicitly!

Such a command then looks like ng generate service some-feature/some-sub-feature/some-service --project some-other-app.

Good, we know we have to add support for project so let’s get to it!

Make it work!

First we have to extend our schema.json (and .d.ts ) files…

Add project property to our schema.json file
Add project property to our Schema interface

Now we have to make adjustments to the implementation of our schematics rule.

It might look like a lot but in the end it’s all about resolving correct name and the location of our newly generated file!

  1. We have to retrieve workspace configuration from angular.json file. The config gives us access to the defaultProject and the projects object itself.
  2. We will use them to retrieve specific project configuration
  3. We will use project configuration to resolve project default path (eg src/app or projects/some-app/src/app in standard Angular CLI workspaces)
  4. We will parse name together with default project path to get hold of final path and the name of the created file.
  5. We will pass final name into the template and use path to move created file in the appropriate location in the virtual tree
Angular Schematics rule adjusted for support of the Angular CLI workspaces

Once we have this adjustment in place we can try to run our schematics in context of Angular CLI workspace.

The main difference is that we can use ng generate command instead of previously used schematics command!

Boom! Our schematics is useful from the Angular CLI workspace, integration is complete!

Improved help support

Angular CLI workspace enables us to run Angular Schematics using ng g command instead of schematics command which is great because it brings full support for the — help flag!

9. Testing schematics

Angular Schematics are very easy to test because of the nature of the virtual Tree representation of the project files. Being able to --dry-run schematics is a perfect fit for the testing.

In general, the test setup will usually consist of creating of an empty Angular CLI workspace and generating of an app (or lib) on which we then can apply the tested schematic.

Excerpt from the Angular Schematics test setup

Then we can add the tests themselves…

Simple Angular Schematics test which checks if the file was generated in the correct location…

Check out real world test implementation as a part of the @angular-extensions/model library which comes with built in schematics with full test coverage!

10. Build and publish custom schematics

Until now we have been building our schematics project using npm run build:watch which we added in the beginning.

  1. Let’s stop that process and run npm rum build command instead
  2. Also let’s have a look into package.json file and change the name of the package to @schematics/hello and version to 1.0.0.
  3. Besides that, we have to remove *.ts line from .npmignore file because it would exclude our template from the final package
  4. Now we could run npm publish but let’s run npm pack instead which will give us schematics-hello-1.0.0.tgz file which we can copy to some Angular CLI workspace project.
  5. Then, in the target Angular CLI workspace we can run npm i --no-save schematics-hello-1.0.0-tgz which will install our package into that project.
  6. The last step is to run schematics by referencing package name instead of the path to local schematics project. We can run ng g @schematics/hello:hello Tomas.

Boom that’s it, we have our first working custom Angular Schematics npm package!

11. Implement schematics as a part of library project

Previously we have been focusing on creating schematics as a stand alone npm package. This can be a great approach for a big collection of schematics dedicated for some project similarly to how @schematics/angular is the default standalone collection of Angular CLI.

In that case we have to integrate the build of schematics next to the library itself. The exact implementation depends on whether we are implementing our lib as a part of Angular CLI workspace or a custom setup.

It the end it boils down to having separate tsconfig.schematics.json which compiles only the schematics project and a way to copy every other schematics asset to the schematics dist folder.

Assets can be copied using a library like cpx as a part of schematics build in npm scripts, for example something like this "schematics:build:copy": "cpx schematics/**/{collection.json,schema.json,files/**} dist/schematics".

Real-life example of this setup can be seen in this package.json file and tsconfig.schematics.json file.

12. Add ng-add support

The ng-add is this cool thing that enables us to run ng add @angular/material instead of doing npm i @angular/material and then performing additional hundred manual setup steps (I am joking 😂, partially… 😬).

It’s extremely cool because it enables us (or consumers of our library) to get started in the matter of seconds instead of spending tens of minutes following some kind of documentation to be able to setup the library properly.

Let’s say we have a library with some nontrivial setup. For example we may have implemented websocket based library for our organization and setup includes selection of connected services. Such a setup can be automated using ng-add.

The only difference between ng-add and other schematics is that they get executed by calling ng add @my-org/schematics instead of ng g @my-org/schematics:ng-add.

The Angular CLI will npm install the mentioned package and execute the ng-add package automatically!

13. Add ng-update support

The ng-update enable us to automate work needed to upgrade to a next version of our library.

Such a “mechanical” change should be possible to automate using some search and replace (or some more advanced AST manipulation).

First of all, the ng-update uses a special dedicated ng-update property in the package.json which references new migration.json file.

Add ng-update property to the package.json

The migration.json file then contains all the migration schematics per version…

Example of migration.json file content

Then the content of the index.ts file would follow in line of standard schematics implementation…

Check out real life example of ng-update implementation of @angular/material library.

We’re finally done, this shit was crazy! 🤪

This is by far the longest and most complicated article I have ever written so your 👏👏👏 would be very appreciated to help it spread to a wider audience 🙏.

Also, don’t hesitate to ping me if you have any questions using the article responses or Twitter DMs @tomastrajan.

And never forget, future is bright

Obviously the bright future! (📷 by Cyrus Pellet)

🅰️ Google Developer Expert for Angular #GDE ❤️ ️Typescript 🛠️ Maker of the @releasebutler and Medium Enhanced Stats 🌞 Obviously the bright Future

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store