You're reading for free via Tomas Trajan's Friend Link. Become a member to access the best of Medium.
Member-only story
Total Guide To Custom Angular Schematics

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!
Feel free to skip straight to the part dealing with the specific topic you are interested in. That way you can fill in the blanks in your pre-existing Angular schematics knowledge!
What are we going to learn?
- Create custom schematics project workspace
- Concepts that describe schematics implementation (Factory, Rule, Tree…)
- Build schematics (and setup watch build for convenience)
- Run schematics (package vs relative path, debug mode, …)
- Implement simple schematics (generate a file, …)
- Parametrize schematics with schema and options
- Use advanced schematics templates (and string helper functions)
- Integrate custom schematics in Angular CLI worksapce
- Test custom schematics
- Build and publish custom schematics package
- Implement schematics as a part of Angular library project
- Add
ng-add
support - Add
ng-update
support
Preparation
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.

The project was generated using
schematics blank
command because it is available in the out of the box available package@schematics/schematics
which gets installed together with@angular-devkit/schematics-cli
which we installed in the beginning…
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.
If we looked into
@schematics/angular
package which is provided by default in every Angular CLI project we would see that itscollection.json
file contains entries likecomponent
orservice
. In other words, everything which we can normally generate using Angular CLI.
Our initial collection.json
file will look like this…

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.
We could also export
hello
function as default export and in that case we could just point factory to./hello/index
file without the need to specify function name…
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.

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
.
We might be curious why do we need a factory function and don’t implement just the rule. The thing is, we want schematic to be useful under variety of different circumstances so we need to be able to adjust them accordingly. That’s why the
hello
function accepts the_options
argument.
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 rule can call additional already implemented rules in its body because it posses everything that is needed for a rule execution. That is the tree and the context. We will see this in action later when we will use some utility rules (eg for template processing) inside of our main rule…
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
In a way, Angular Schematics use concept which is similar to what React does with the virtual DOM. The only difference being that with Schematics we are working with the file system…
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
.
Running build manually after every change would be a tedious task so let’s implement
build:watch
npm script instead!

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

Our
hello
schematic just returns the tree and hence no changes are performed andNothing to be done
message is displayed in the console…
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.

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!

As we can see, the schematics output says that the file
/hello.js
was created but when we list content of the folder the file is nowhere to be found.
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.
I personally usually go with
--debug=false
because it is easier to write 😉.

As a side note, if we tried to run this command again we would encounter error saying that the
Path "/hello.js" already exist
.⚠️ This is usually solved by adding
--force
flag to the command execution but it would not help in our case. For now, we have to delete the file manually.✔️ The
--force
flag does work in most situation when schematic is implemented using templates which we will explore later.
🤫 Psst! Do you think that NgRx or Redux are overkill for your needs? Looking for something simpler? Check out @angular-extensions/model library!

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

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.

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.
In our file we specified one option, the
name
which is a positional option so that we can use it without specifying the flag itself (without--name
).
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.

If we now tried to execute our
hello
schematic with unsupported option, let’s sayname1
, we would encounter followingError: Schematic input does not validate against the Schema: { "name1": "Tomas" }
. This is great because it means our options validation is working!
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…

The
schema.d.ts
can also be generated automatically by using packages likedtsgenerator
. We can try navigating to./src/hello
(the folder which containsschema.json
) and runningnpx -p dtsgenerator dtsgen schema.json -o schema.d.ts
which will generate our.d.ts
file automatically!
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).

The prompt will provide developer with correct input type based on the option type. This is great because we will get
yes/no
forboolean
and selection list for theenum
option types!
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.
⚠️ We have to use
./files/
folder because it will be excluded from the Typescript compilation by default (seetsconfig.json
in the root of the project). The folder has to be excluded because we don’t want to compile the templates!✔️ If we wanted to use
./templates/
folder we would have to adjusttsconfig.json
accordingly!
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.
The
__
(double underscore) is a delimiter which separatesname
variable from rest of the normal string. Thedasherize
is a helper function which will receive value of thename
variable and convert it to kebab case string and the@
is a way to apply variable to a helper function.
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.

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

Thanks to Alexander K. J. Schmidt for great feedback about the issue he encountered on a mac: “ I had to
rm .DS_Store
in the directoryhello/
sometimes” and also, to make the compiler happy you will have to use secondreturn mergeWith(sourceParametrizedTemplate)(tree, _context)
to prevent Typescript compiler from complaining about unusedtree
variable!
With these adjustments in place we can try it by running schematics .:hello 'Tomas Trajan' --debug=false

Notice, we put name into quotes so that we can include space between the first and the last name to demonstrate how well it will be handled by the
dasherize
helper function which produces correcthello-tomas-trajan.ts
file.
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!

✔️ Once we start using templates we can also use
--force
flag which will overwrite previously generated folders and files with same name which comes very handy when iterating on template content during development.⚠️ Note that
--force
flag does NOT work when creating files withtree.create()
method…
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…

Such a template would generate following file if called with the 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
.


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

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…

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 Angular CLI workspace consists of zero to possibly many applications and libraries. This brings us to the concept of
project
.
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…


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!
- We have to retrieve workspace configuration from
angular.json
file. The config gives us access to thedefaultProject
and theprojects
object itself. - We will use them to retrieve specific project configuration
- We will use project configuration to resolve project default path (eg
src/app
orprojects/some-app/src/app
in standard Angular CLI workspaces) - We will parse name together with default project path to get hold of final path and the name of the created file.
- We will pass final name into the template and use path to move created file in the appropriate location in the virtual tree

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 usedschematics command!

Improved help support
✔️ The
ng generate
comes also with additional huge benefit of support for the--help
flag which will now correctly print info which we provided in theschema.json
file.⚠️ This unfortunately doesn’t work when used with basic
schematics
command!

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.
That way we can always run given schematic as if for real and check if the desired changes really happened in the tree!
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.

Then we can add the tests themselves…

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.
- Let’s stop that process and run
npm rum build
command instead - Also let’s have a look into
package.json
file and change the name of the package to@schematics/hello
and version to1.0.0
. - Besides that, we have to remove
*.ts
line from.npmignore
file because it would exclude our template from the final package - Now we could run
npm publish
but let’s runnpm pack
instead which will give usschematics-hello-1.0.0.tgz
file which we can copy to some Angular CLI workspace project. - 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. - 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
.
✔️ Notice that we don’ have to use
--debug=false
flag!
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.
On the other hand, it can also make sense to have inline smaller collection of schematics as a part of a library itself where it provides stuff specific to that library.
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.
⚠️ As always, it depends… Some libs require little to none configuration to get started so they would not benefit much by implementing
ng-add
support…
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
ng-add
is a schematic that is implemented in a same way as any other schematic so all the steps we disused above are still valid! More so,ng-add
schematics usually live side by side other supported schematics in the collection.
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.
Let’s say we have an upcoming major version of our library with a breaking change to API of one of the main services. For example, the service API was 5 arguments but we want to change it to an options object.
Such a “mechanical” change should be possible to automate using some search and replace (or some more advanced AST manipulation).
🔧 I haven’t implemented any
ng-update
schematics yet so the following content is based solely on the reverse engineering of the @angular/material library…
First of all, the ng-update
uses a special dedicated ng-update
property in the package.json
which references new migration.json
file.

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

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! 🤪
I hope you enjoyed this epic ride and will now be able to implement and use tailor made Angular Schematics to make you and your team even more productive!
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.

Do you find the information in this article useful?
We are providing tailored expert support for developing of your Angular applications. Explore our wide range offers on angularexperts.ch
And never forget, future is bright

Starting an Angular project? Check out Angular NgRx Material Starter!

If you made it this far, feel free to check out some of my other articles about Angular and frontend software development in general…