Extend Angular Schematics to customize your development process
Have you ever realized that you often repeat the same patterns across multiple files? Creating schematics that override well-known Angular schematics, designing them based on project needs will enhance your development and reduce time spent on generating files.

Extend Angular Schematics to customize your development process
Have you ever realized that you often repeat the same patterns across multiple files? Creating schematics that override well-known Angular schematics, designing them based on project needs will enhance your development and reduce time spent on generating files.


Have you ever realized that you often repeat the same patterns across multiple files, classes? It could be a simple task like adding a ngOnDestroy method to every component you create to manage subscriptions or adding HttpClient to almost every service you make. I bet you have!
I can assure you, there is nothing wrong with that - this will not be an article about DRY and how to remove duplicates in your project using OOP tricks. Sometimes creating a complex, nested architecture just to remove some duplicated elements is not enough or is not the best you can do - simplicity and readability of your classes are as important. This is a scenario where Angular Schematics are becoming incredibly useful.
SchematicsLink to this section
I think every Angular developer uses Angular Schematics, at least when creating components or services, but if you are not familiar with this valuable tool - quick explanation:
Angular Schematics offers dozens of commands that you could use to generate files, run migrations, update files, etc. For instance, to create a component directory, along with the template, stylesheet, and component class file, you can simply run ng generate component my-component
in the terminal or use the short version ng g c my-component
. Schematics will take care of everything and produce three files with example code. Pretty helpful, right?
Why should I be interested?Link to this section
What if I told you that, in many projects, developers need to create files almost every time with some custom configuration? To better explain that, I will give you a few examples:
- most of the components use NGRX Store to dispatch actions or gather data to show it in the template. So developers create components using the
ng g c
command and manually inject theStore
class into a component constructor. - almost every component rely on Observables, and therefore it needs to manage subscriptions created within. How do developers usually go about this? They have to do a private field that will gather all subscriptions together, add another interface that the class implements - OnDestroy, create a method
ngOnDestroy
, and finally close all subscriptions in thengOnDestroy
method. - services are used for REST communication with API, so they have to have an injected
HttpClient
. I guess you already know what developers have to do after every service creation.
Doesn’t that sound to you like a bit of wasting a developer's time, possibly introducing bugs and inconsistency between classes? Imagine a situation where one developer names HttpClient
object as http
and another developer likes httpClient
more ( project will end up with at least two words for the same thing across the solution), not very readable.
Do I have to create everything on my own?Link to this section
These examples are perfect for discussing the details of our topic. Schematics not only offers predefined commands but also gives us the possibility to design our own!
When I was thinking about implementing custom schematics, I was worried the most about - what if I don't want to create my schematics from scratch? The Angular ones are fine. I just need to extend it a little bit - add some custom code into them. Just like in the examples I presented you above. They are not about creating something new but instead extending the default configuration that Angular Schematics gives us.
Schematics have an answer for this problem - we can use existing schematics and easily override what we want without implementing everything by ourselves.
This article aims to focus on overriding well-known schematics, making them custom based on project needs.
What is the plan?Link to this section
I want to go through the implementation of custom schematics that overrides the default ones. I will start from the very beginning - creating a library, implementing an actual schematic for the component, and finally, schematics configuration for the Angular project. Beware, it will not be an article about all Schematics foundations - I will instead focus on our particular problem. There are a couple of other great articles about the Schematics basics and the docs that are especially good and make learning Schematics very easy.
Let's dive into code!Link to this section
The implementation below will address one of the examples I showed you earlier - my goal would be to design a schematic that will override the default component schematic with automatically generated code for managing rxjs subscriptions.
Creating the libraryLink to this section
We will begin by creating a library for schematics. Firstly, we need to install schematics-cli
:
<>Copynpm install -g @angular-devkit/schematics-cli
Using the schematics-cli
, we are now ready to create a schematics project:
<>Copyschematics blank --name=subscription-component
The blank schematic command will create a project with configured typescript, package.json, and initial schematic. The structure of the project should look like this:
<>Copysubscription-component/ src/ subscription-component/ index.ts index_spec.ts collection.json package.json tsconfig.json
Aside from standard files that you recognize for sure, there are two worth explaining briefly:
- collections.json - this is the main file, in which are defined all schematics that this project will expose
- subscription-component/index.ts - this is the main file of our schematic, it contains a factory function which Schematics will use for generating our component
If you take a closer look into the collection.json file, you will see it includes our schematic and points directly to the factory function from index.ts.
<>Copy{ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "subscription-component": { "description": "A blank schematic.", "factory": "./subscription-component/index#subscriptionComponent" } } }
Now, what's inside a factory function?
<>Copyexport function subscriptionComponent(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { return tree; }; }
Let’s briefly understand the factory function’s critical elements, which we will later use during the implementation.
_options
- an object that keeps all input data from a caller. We will use it to get additional inputs as well as the name of the componentRule
- it’s an object that defines the transformations of the Tree. All we need to know, for now, is that we will need to build that, and this will precisely define files generation with the rules applicable to them.
Let's now focus on implementing our custom component schematic.
Implementing component schematicLink to this section
Let's start by considering what we will need from the user as input to a schematic script? For our basic implementation, the only two crucial parameters are the name of the component that we will generate and the directory path.
SchemaLink to this section
To define input parameters, we need to create an additional file called schema.json in the schematic directory (so inside /subscription-component). Schema can contain many practical fields for describing the behavior of the script. Here is a straightforward example for our needs:
<>Copy{ "$schema": "http://json-schema.org/schema", "id": "SubscriptionComponentSchema", "type": "object", "properties": { "path": { "type": "string", "format": "path", "visible": false }, "name": { "type": "string", "$default": { "$source": "argv", "index": 0 } } }, "required": [ "name" ] }
Before-mentioned Schema will provide us with the very same result as the default ng generate component
schematic input. When someone runs in the terminal ng generate component shared-components/custom-dialog
, it will set the property path to shared-components/
and name to custom-dialog
. That's all we need. The Schema should be included in the schematic definition in the collection file by adding such field:
<>Copy"schema": "./subscription-component/schema.json"
Now we can focus on the factory’s actual implementation, so let's take a look at the index.ts file.
FactoryLink to this section
We want to implement a factory function to read input parameters and then use the default component generation factory to generate all files for the component, except the ones that we will override.
How does the file generation work without getting into details? For each file generated via Schematics, the factory function is provided with a template file. The template file contains a definition of how the file should be constructed.
So how will we use that? We will simply create a template file for the typescript component class and merge that with other files generated by default Schematics.
First step: format input parameters so they can be safely used to creating files:
<>Copy_options.name = basename(_options.name); _options.path = normalize('/' + dirname((_options.path + '/' + _options.name)));
In case you've forgotten _options
is the argument of the factory that keeps all input parameters. The functions for formatting these parameters are all taken from @angular-devkit
package.
Second step: create template source for typescript component class
<>Copyconst templateSource = apply( url('./files'), [ template(_options), move(normalize(_options.path)), ], );
Directory ./files will contain the file with template definition. How does it work?
Schematics creates template sources based on the static files from provided directory and rules applied to them. The set of rules defines what to do with the Source. Our example creates a template using the _options
that keep dynamic data and then move all sources to the path.
Third, the last step: merge schematic factories.
In the end, we need to return the Rule object. In our case, it will be a Rule created based on the default component generation Rule merged with our Source.
<>Copyreturn chain([ externalSchematic('@schematics/angular', 'component', _options), mergeWith(templateSource, MergeStrategy.Overwrite), ]);
Function externalSchematic
creates a Rule from any external schematics - in our case, the default Angular ones. Then uses mergeWith
function to merge rules, using the appropriate merging strategy provided as the second argument. That creates for us the final Rule that will generate an entire component for us.
Factory function should finally look like this:
<>Copyexport function subscriptionComponent(_options: any): Rule { return (_tree: Tree, _context: SchematicContext) => { _options.name = basename(_options.name); _options.path = normalize('/' + dirname((_options.path + '/' + _options.name))); const templateSource = apply( url('./files'), [ template(_options), move(_options.path), ], ); return chain([ externalSchematic('@schematics/angular', 'component', _options), mergeWith(templateSource, MergeStrategy.Overwrite), ]); }; }
Now it's time to write our template!
TemplateLink to this section
Writing a template is a relatively easy job because it should look the same as well known generated file, except for one difference - all dynamic content, for example, the name of a component, has to be written inside special tags (<%=
, %>
), to print the value.
Template files are stored inside /files directory, and their names should be written using a specific format to allow dynamic naming of the files. For our case, we want to create a typescript file that will be in the form component-name.component.ts
, assuming the "component-name" is our name provided as an input. To fulfill that, we need to create a template file with the name: __name@dasherize__.component.ts
. Double underscore separates the dynamic content from the plain string, and the dasherize
is an Angular function that will make a "kebab-case" string from the name. We have to use the same approach for naming a directory, so we need to place a template file within a folder called __name@dasherize__
.
Now, the actual template:
<>Copyimport { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-<%= dasherize(name) %>-component', templateUrl: './<%= dasherize(name) %>.component.html', styleUrls: ['./<%= dasherize(name) %>.component.scss'], }) export class <%= classify(name) %>Component implements OnInit { constructor() { } ngOnInit(): void { } }
I think you recognize the pattern? It’s a well-known component class but with the use of dynamic strings inside the special tags.
For the files and the selector, I'm using the dasherize(name)
function to make a kebab-case name. Keep in mind that this function is not provided for the template yet - we will need to add it. The class name uses classify(name)
function that converts the name to be in upper camelCase format - we have to provide this one too. So, to sum up, we need to provide two functions and the name of a component.
Let's look at the template source creation function:
<>Copytemplate(_options),
It's pretty simple. As you see, it already contains the name
variable for us (inside _options
object). That means we only need to provide a classify
and dasherize
functions. Let's do that!
<>Copytemplate({ ..._options, classify: strings.classify, dasherize: strings.dasherize, }),
strings
is the collection from the @angular-devkit
package, so the only thing we need to do is expose items we want to use.
Now we can extend that simple template with our subscription management code. That is our goal, right? There are a couple of ways to control subscriptions, and I will use the one with the Subscription
field to keep all subscriptions and unsubscribe that on destroy hook.
That's how the template of the class looks like:
<>Copyexport class <%= classify(name) %>Component implements OnInit, OnDestroy { private readonly subscription: Subscription = new Subscription(); constructor() { } ngOnInit(): void { } ngOnDestroy(): void { this.subscription.unsubscribe(); } }
Collection - final schematic configurationLink to this section
I want to override the default ng g c
/ ng generate component
schematic, so inside collection.json, I have to modify the name to be “component” and add the alias “c”.
<>Copy"component": { "aliases": [ "c" ], "factory": "./subscription-component/index#subscriptionComponent", "schema": "./subscription-component/schema.json" }
We are running a schematic!Link to this section
There are a couple of ways to test schematics and its output. For this article’s purpose, I will use the easiest way to add custom schematics to the existing Angular project to run schematics without further tests.
We need to link our schematics directory to the Angular project and set it as the default schematics collection to override @angular/schematics
.
First things first, we need to build our library with a schematic. Use the command below in the library directory:
<>Copynpm run build
To add our schematics project as a dependency for Angular run in the project directory:
<>Copynpm install --save-dev ../path/to/subscription-component
To override the default collection, you need to add it in the angular.json file as a cli property in the main object.
<>Copy"cli": { "defaultCollection": "subscription-component" }
Then in the same file, we need to provide default options, in the same way they are supplied for @angular/schematics
:
<>Copy"schematics": { "@schematics/angular:component": { "style": "scss" }, "subscription-component:component": { "style": "scss" } },
Ready?Link to this section
No more configuration, I promise. Now you should be able to run your custom schematic to generate a component with project personalized content!
<>Copyng g c app/my-component
The result should look like this, and you should be a proud owner of a brand-new component!
<>CopyCREATE src/app/my-component/my-component.component.scss (0 bytes) CREATE src/app/my-component/my-component.component.html (21 bytes) CREATE src/app/my-component/my-component.component.spec.ts (626 bytes) CREATE src/app/my-component/my-component.component.ts (498 bytes) UPDATE src/app/app.module.ts (470 bytes)
Great job! You did it! Check the component.ts file for the subscription management code.
ImprovementsLink to this section
There are many ways to improve our basic schematic, I will shortly describe some ideas later, but I want to change one more thing to make our schematic more useful in daily development.
I assume that although it could be the case that creating components with subscription handling is pretty common, it won't be useful in every case. I want to improve the schematic to take an additional parameter that will create a component with or without the subscription handing. Let's say it will be by default turned to true, but we will give the ability to disable the subscription control code.
Schema.json updateLink to this section
I will add additional parameter definition:
<>Copy"subscriptionManagement": { "description": "Include subscription management code in the component class", "type": "boolean", "default": true, "alias": "subscription" }
TemplateLink to this section
Now inside the template file, we will use something called conditional templates. This technique will generate various fragments of a template based on the dynamic data passed in _options
. The options object already contains the subscriptionManagement
flag because it is an input parameter, so we don’t need to change the factory.
Conditional templates use the similar syntax as discussed in previous steps. The keywords we can use for building conditions are if
and else
. So in our case, we can implement it like this:
<>Copyexport class <%= classify(name) %>Component implements OnInit <% if (subscriptionManagement) {%>, OnDestroy <% }%> { <% if (subscriptionManagement) {%> private readonly subscription: Subscription = new Subscription(); <% }%> constructor() { } ngOnInit(): void { } <% if (subscriptionManagement) {%> ngOnDestroy(): void { this.subscription.unsubscribe(); } <% }%> }
That's all! Now build the schematic, install it and check how it works.
<>Copyng g c app/my-component
<>Copyng g c app/my-component --subscription=false
Extra improvementsLink to this section
I mentioned above that there are a couple of things that you could improve when working on your schematics. Here is the list:
- use the NPM registry to publish your schematics. It will make it easier to add your schematic for other developers
- provide
ng add
support for your library. Remember when we had to set the default collection in angular.json manually? It could be all done automatically if you make another schematic for theng add
command - use migration schematics to help developers when introducing some breaking changes
- add unit tests! I guess I don't need to say why
- extend factory function to use default angular project options, like styles, selector prefix, etc.
SummaryLink to this section
I hope you found this article interesting and you already have some ideas about how you could apply it in your project to enhance the development process. I firmly encourage you to do it! It shouldn't take much effort - begin with some minimal value concept, include in schematic one of the critical snippets that is copied over and over, and by time add more.
To remind you briefly what exactly we have done, here is a short list of steps to implement a custom schematic that overrides the default angular one:
- create a blank schematic using the built-in command
- implement schematic
- factory function
- template file
- schema with input parameters definitions
- collection, which defines exposed schematics - add schematic to Angular project
It's not a long process, so if you feel it could be useful for you, give it a go! You won’t be disappointed.
If you feel that you are missing some implementation details (which I hope you won't), here is a link to the Github repository with a working example from this article.
That's all. Thanks for staying with me!
Enjoy spending less time on copy and paste and more on an actual development! This is the way.
Comments (0)
Be the first to leave a comment
About the author

Hey, I'm a frontend developer, passionate about good design for both the code and UX/UI side of things. I am mainly involved in Angular, rxjs, typescript subjects.

About the author
Maciej Wojcik
Hey, I'm a frontend developer, passionate about good design for both the code and UX/UI side of things. I am mainly involved in Angular, rxjs, typescript subjects.
About the author

Hey, I'm a frontend developer, passionate about good design for both the code and UX/UI side of things. I am mainly involved in Angular, rxjs, typescript subjects.