What are Schematics?
Schematics are template-based code generators that support complex logic. They are the engine behind ng generate commands and ng add package installations. Schematics can create, modify, delete, and move files while applying rules and templates to scaffold or modify code.
Think of schematics as “code recipes” - they describe how to generate or transform code in a repeatable, configurable way.
How Schematics Work
Schematics operate on a virtual file system called a tree . Changes are staged in memory before being committed to disk, allowing for validation and rollback.
Tree Virtual file system representing project structure
Rule Transformation applied to the tree
Template File template with placeholders
Action Create, update, delete, or rename operations
Official Schematics
Angular provides built-in schematics for common tasks:
Code Generation Schematics
Component
Service
Module
Directive
Pipe
Guard
ng generate @schematics/angular:component user-profile
Generates:
Component TypeScript file
Component template (HTML)
Component styles (CSS/SCSS)
Component test file
Options:
--standalone: Create standalone component
--inline-template: Use inline template
--inline-style: Use inline styles
--skip-tests: Skip test file
--flat: Don’t create folder
--export: Export from module
ng generate @schematics/angular:service auth
Generates:
Service TypeScript file
Service test file
Options:
--skip-tests: Skip test file
--flat: Don’t create folder
ng generate @schematics/angular:module admin
Generates:
Module TypeScript file
Optional routing module
Options:
--routing: Add routing module
--flat: Don’t create folder
--module: Parent module to import into
ng generate @schematics/angular:directive highlight
Generates:
Directive TypeScript file
Directive test file
Options:
--standalone: Create standalone directive
--skip-tests: Skip test file
--flat: Don’t create folder
ng generate @schematics/angular:pipe currency-format
Generates:
Pipe TypeScript file
Pipe test file
Options:
--standalone: Create standalone pipe
--skip-tests: Skip test file
--flat: Don’t create folder
ng generate @schematics/angular:guard auth
Generates:
Guard TypeScript file (functional by default)
Guard test file
Options:
--functional: Generate functional guard (default)
--skip-tests: Skip test file
Migration Schematics
Angular provides powerful migration schematics:
Convert components, directives, and pipes to standalone: ng generate @angular/core:standalone
Three-phase migration:
Convert to Standalone
Convert declarations to standalone and update imports
Remove NgModules
Delete unnecessary NgModule classes
Update Bootstrap
Switch to bootstrapApplication API
Options:
mode: Migration mode (convert, prune, bootstrap)
path: Relative path to migrate
See the standalone migration README for details.
Migrate to new control flow syntax: ng generate @angular/core:control-flow-migration
Transforms:
*ngIf → @if
*ngFor → @for
*ngSwitch → @switch
Before: < div *ngIf = "show" > Content </ div >
< li *ngFor = "let item of items" > {{ item }} </ li >
After: @if (show) {
< div > Content </ div >
}
@for (item of items; track item) {
< li > {{ item }} </ li >
}
Migrate to signal-based APIs: Signal Inputs: ng generate @angular/core:signal-input-migration
Signal Queries: ng generate @angular/core:signal-queries-migration
Output Migration: ng generate @angular/core:output-migration
NgStyle to Style: ng generate @angular/core:ngstyle-to-style-migration
NgClass to Class: ng generate @angular/core:ngclass-to-class-migration
Inject Migration: ng generate @angular/core:inject-migration
Router Testing Module: ng generate @angular/core:router-testing-module-migration
Cleanup Unused Imports: ng generate @angular/core:cleanup-unused-imports
All migration schematics support a --path option to migrate specific directories.
Creating Custom Schematics
Build your own code generators for common patterns in your application.
Setup
Install Schematics CLI
npm install -g @angular-devkit/schematics-cli
Create Schematics Collection
schematics blank my-schematics
cd my-schematics
npm install
Project Structure
my-schematics/
├── src/
│ ├── my-schematic/
│ │ ├── index.ts # Schematic logic
│ │ ├── schema.json # Options schema
│ │ ├── schema.d.ts # TypeScript interface
│ │ └── files/ # Templates
│ │ └── __name@dasherize__/
│ │ ├── __name@dasherize__.component.ts
│ │ └── __name@dasherize__.component.html
│ └── collection.json # Collection metadata
├── package.json
└── tsconfig.json
Define Schema
schema.json:
{
"$schema" : "http://json-schema.org/schema" ,
"$id" : "MySchematic" ,
"type" : "object" ,
"properties" : {
"name" : {
"type" : "string" ,
"description" : "The name of the component" ,
"$default" : {
"$source" : "argv" ,
"index" : 0
}
},
"path" : {
"type" : "string" ,
"format" : "path" ,
"description" : "The path to create the component" ,
"visible" : false
},
"project" : {
"type" : "string" ,
"description" : "The name of the project"
},
"export" : {
"type" : "boolean" ,
"default" : false ,
"description" : "Export the component from the module"
}
},
"required" : [ "name" ]
}
schema.d.ts:
export interface Schema {
name : string ;
path ?: string ;
project ?: string ;
export ?: boolean ;
}
Implement Schematic
index.ts:
import {
Rule ,
SchematicContext ,
Tree ,
apply ,
url ,
template ,
mergeWith ,
move ,
chain ,
SchematicsException ,
} from '@angular-devkit/schematics' ;
import { strings , normalize , experimental } from '@angular-devkit/core' ;
import { Schema } from './schema' ;
export function mySchematic ( options : Schema ) : Rule {
return ( tree : Tree , context : SchematicContext ) => {
// Validate options
if ( ! options . name ) {
throw new SchematicsException ( 'Option "name" is required.' );
}
// Get workspace
const workspaceConfig = tree . read ( '/angular.json' );
if ( ! workspaceConfig ) {
throw new SchematicsException ( 'Could not find Angular workspace configuration' );
}
// Parse workspace
const workspace : experimental . workspace . WorkspaceSchema = JSON . parse (
workspaceConfig . toString ()
);
// Determine project
if ( ! options . project ) {
options . project = workspace . defaultProject ;
}
const project = workspace . projects [ options . project ! ];
if ( ! project ) {
throw new SchematicsException ( `Project " ${ options . project } " not found.` );
}
// Determine path
if ( options . path === undefined ) {
options . path = ` ${ project . sourceRoot } /app` ;
}
// Apply template
const templateSource = apply ( url ( './files' ), [
template ({
... strings ,
... options ,
}),
move ( normalize ( options . path as string )),
]);
return chain ([
mergeWith ( templateSource ),
// Add additional rules here
])( tree , context );
};
}
Create Templates
files/__name@dasherize__/__name@dasherize__.component.ts :
import { Component } from '@angular/core' ;
@ Component ({
selector: 'app-<%= dasherize(name) %>' ,
templateUrl: './<%= dasherize(name) %>.component.html' ,
styleUrls: [ './<%= dasherize(name) %>.component.<%= style %>' ],
standalone: true
})
export class <% = classify ( name ) %> Component {
constructor () { }
}
files/__name@dasherize__/__name@dasherize__.component.html :
< div class = " < %= dasherize(name) %>-container" >
< h2 > < %= classify(name) %> Component </ h2 >
< p > < %= dasherize(name) %> works! </ p >
</ div >
Template Functions
Angular provides utility functions for string transformation:
Function Input Output Description dasherizeMyComponentmy-componentConvert to kebab-case classifymy-componentMyComponentConvert to PascalCase camelizemy-componentmyComponentConvert to camelCase capitalizehelloHelloCapitalize first letter underscoreMyComponentmy_componentConvert to snake_case
Schematic Rules
Rules define transformations applied to the tree:
Basic Rules
import { Rule , Tree } from '@angular-devkit/schematics' ;
// Create a file
export function createFile () : Rule {
return ( tree : Tree ) => {
tree . create ( 'new-file.ts' , 'console.log("Hello");' );
return tree ;
};
}
// Read a file
export function readFile () : Rule {
return ( tree : Tree ) => {
const content = tree . read ( 'existing-file.ts' );
if ( content ) {
console . log ( content . toString ());
}
return tree ;
};
}
// Update a file
export function updateFile () : Rule {
return ( tree : Tree ) => {
const recorder = tree . beginUpdate ( 'file.ts' );
recorder . insertLeft ( 0 , '// File header \n ' );
tree . commitUpdate ( recorder );
return tree ;
};
}
// Delete a file
export function deleteFile () : Rule {
return ( tree : Tree ) => {
tree . delete ( 'old-file.ts' );
return tree ;
};
}
Chaining Rules
import { chain , Rule } from '@angular-devkit/schematics' ;
export function mySchematic () : Rule {
return chain ([
createFile (),
updateFile (),
deleteFile (),
]);
}
Conditional Rules
import { Rule , SchematicContext } from '@angular-devkit/schematics' ;
export function conditionalRule ( options : Schema ) : Rule {
return ( tree : Tree , context : SchematicContext ) => {
if ( options . export ) {
context . logger . info ( 'Exporting component...' );
// Add export logic
}
return tree ;
};
}
Advanced Features
Working with AST
Modify TypeScript files using Abstract Syntax Trees:
import * as ts from 'typescript' ;
import { InsertChange } from '@schematics/angular/utility/change' ;
function addImport ( tree : Tree , filePath : string ) : Tree {
const source = tree . read ( filePath );
if ( ! source ) return tree ;
const sourceFile = ts . createSourceFile (
filePath ,
source . toString (),
ts . ScriptTarget . Latest ,
true
);
// Find where to insert import
const allImports = sourceFile . statements . filter (
node => ts . isImportDeclaration ( node )
);
const lastImport = allImports [ allImports . length - 1 ];
const insertPos = lastImport ? lastImport . end + 1 : 0 ;
// Create change
const change = new InsertChange (
filePath ,
insertPos ,
" \n import { Component } from '@angular/core'; \n "
);
// Apply change
const recorder = tree . beginUpdate ( filePath );
recorder . insertLeft ( change . pos , change . toAdd );
tree . commitUpdate ( recorder );
return tree ;
}
External Schematics
Call other schematics from your schematic:
import { externalSchematic , Rule , chain } from '@angular-devkit/schematics' ;
export function mySchematic ( options : Schema ) : Rule {
return chain ([
// Call Angular's component schematic
externalSchematic ( '@schematics/angular' , 'component' , {
name: options . name ,
standalone: true ,
}),
// Add custom logic
customRule ( options ),
]);
}
Task Scheduling
Schedule tasks to run after schematic completes:
import { Rule , SchematicContext } from '@angular-devkit/schematics' ;
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ;
export function mySchematic () : Rule {
return ( tree : Tree , context : SchematicContext ) => {
// Schedule npm install
context . addTask ( new NodePackageInstallTask ());
return tree ;
};
}
Testing Schematics
Test schematics using the testing utilities:
import { SchematicTestRunner , UnitTestTree } from '@angular-devkit/schematics/testing' ;
import * as path from 'path' ;
describe ( 'my-schematic' , () => {
const collectionPath = path . join ( __dirname , '../collection.json' );
const runner = new SchematicTestRunner ( 'schematics' , collectionPath );
let appTree : UnitTestTree ;
beforeEach (() => {
appTree = runner . runExternalSchematic (
'@schematics/angular' ,
'workspace' ,
{ name: 'workspace' , version: '14.0.0' }
);
});
it ( 'should create component files' , async () => {
const options = { name: 'test' };
const tree = await runner . runSchematic ( 'my-schematic' , options , appTree );
expect ( tree . files ). toContain ( '/test/test.component.ts' );
expect ( tree . files ). toContain ( '/test/test.component.html' );
});
it ( 'should have correct content' , async () => {
const options = { name: 'test' };
const tree = await runner . runSchematic ( 'my-schematic' , options , appTree );
const content = tree . readContent ( '/test/test.component.ts' );
expect ( content ). toContain ( 'class TestComponent' );
expect ( content ). toContain ( 'standalone: true' );
});
});
Publishing Schematics
Test Locally
npm link
cd /path/to/test-project
npm link my-schematics
ng generate my-schematics:my-schematic test
Use in Projects
ng add my-schematics
ng generate my-schematics:my-schematic test
Collection Configuration
collection.json:
{
"$schema" : "../node_modules/@angular-devkit/schematics/collection-schema.json" ,
"schematics" : {
"my-schematic" : {
"description" : "Generate a custom component" ,
"factory" : "./my-schematic/index#mySchematic" ,
"schema" : "./my-schematic/schema.json" ,
"aliases" : [ "ms" ],
"hidden" : false
},
"ng-add" : {
"description" : "Add my-schematics to the project" ,
"factory" : "./ng-add/index#ngAdd" ,
"schema" : "./ng-add/schema.json"
}
}
}
Path to schematic implementation and export name
Path to JSON schema file defining options
Alternative names for the schematic
Hide from ng generate list
Best Practices
Always validate options and check for existing files: if ( ! options . name ) {
throw new SchematicsException ( 'Name is required' );
}
if ( tree . exists ( targetPath )) {
throw new SchematicsException ( `File already exists: ${ targetPath } ` );
}
Provide helpful feedback: context . logger . info ( 'Creating component...' );
context . logger . warn ( 'This will overwrite existing files' );
context . logger . error ( 'Failed to create component' );
context . logger . debug ( 'Debug information' );
Test with --dry-run flag: ng generate my-schematics:my-schematic test --dry-run
Shows what would be generated without creating files.
Use normalize and join for cross-platform compatibility: import { normalize , join } from '@angular-devkit/core' ;
const targetPath = normalize ( join ( options . path , options . name ));
Next Steps
CLI Overview Return to CLI overview
Builders Learn about custom builders
Resources
Schematics Guide Official schematics documentation
Angular Schematics Official Angular schematics source
DevKit Schematics Schematics DevKit source
Example Schematics Angular Core migration schematics