Angular Schematics 🏭
Angular Schematics are code generators and transformers that power the Angular CLI. Every time you run ng generate component or ng add @angular/material, you’re using schematics behind the scenes. You can also create your own custom schematics to automate repetitive tasks, enforce team conventions, and scaffold complex feature structures.
🎯 What Are Schematics?
Section titled “🎯 What Are Schematics?”A schematic is a template-based code generator that:
- Creates files — Generates new components, services, modules, etc.
- Transforms existing code — Modifies files, updates imports, adds configuration
- Runs migrations — Updates your codebase when libraries change APIs
Schematics operate on a virtual file system (called a Tree), which means changes are staged and applied atomically. If a schematic fails partway through, no files are modified.
🏗️ Built-in Schematics
Section titled “🏗️ Built-in Schematics”The Angular CLI ships with schematics for all common code generation tasks:
Generation Schematics
Section titled “Generation Schematics”# Componentsng generate component features/user-profileng g c shared/ui/button --inline-template --skip-tests
# Servicesng generate service core/services/auth
# Directivesng generate directive shared/directives/highlight
# Pipesng generate pipe shared/pipes/truncate
# Guardsng generate guard core/guards/auth
# Interceptorsng generate interceptor core/interceptors/logging
# Interfaces and Enumsng generate interface models/userng generate enum models/role
# Environment filesng generate environmentsCommon Options
Section titled “Common Options”Most generation schematics share these options:
| Option | Description |
|---|---|
--dry-run / -d | Preview without writing files |
--skip-tests | Don’t generate .spec.ts file |
--flat | Don’t create a subdirectory |
--inline-template / -t | Put template in the .ts file |
--inline-style / -s | Put styles in the .ts file |
--prefix | Override selector prefix |
📦 Using Third-Party Schematics
Section titled “📦 Using Third-Party Schematics”The ng add command installs a package and runs its schematic to configure your project:
ng add @angular/materialThis goes beyond npm install — it modifies angular.json, updates styles.css, adds imports, and sets up theming.
Popular Third-Party Schematics
Section titled “Popular Third-Party Schematics”| Package | What it does |
|---|---|
@angular/material | Sets up Material Design components and theming |
@angular/pwa | Adds Progressive Web App support (service worker, manifest) |
@angular-eslint/schematics | Adds ESLint configuration |
@ngrx/store | Sets up NgRx state management |
@nx/angular | Adds Nx workspace tools |
🔧 Creating Custom Schematics
Section titled “🔧 Creating Custom Schematics”Custom schematics let you automate your team’s specific patterns. Let’s walk through the process.
Install the schematics CLI and create a new collection:
npm install -g @angular-devkit/schematics-clischematics blank --name=my-schematicscd my-schematicsThis generates a project structure:
my-schematics/├── src/│ ├── my-schematics/│ │ ├── index.ts # Schematic entry point│ │ └── index_spec.ts # Tests│ └── collection.json # Schematic collection definition├── package.json└── tsconfig.jsonCollection Schema
Section titled “Collection Schema”The collection.json file defines all schematics in your package:
{ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "feature": { "description": "Generate a feature module scaffold", "factory": "./feature/index#feature", "schema": "./feature/schema.json" }, "api-service": { "description": "Generate an API service with CRUD methods", "factory": "./api-service/index#apiService", "schema": "./api-service/schema.json" } }}Rule and Tree Concepts
Section titled “Rule and Tree Concepts”Schematics are built around two core concepts:
- Tree — A virtual file system representing your project. You read, create, modify, and delete files in the Tree. Changes are only applied after the schematic succeeds.
- Rule — A function that takes a Tree and returns a Tree (or an Observable/Promise of a Tree). Rules are the building blocks of schematics.
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
export function mySchematic(options: MyOptions): Rule { return (tree: Tree, context: SchematicContext) => { // Read a file const content = tree.read('src/app/app.config.ts');
// Create a file tree.create('src/app/new-file.ts', 'export const value = 42;');
// Modify a file const recorder = tree.beginUpdate('src/app/app.config.ts'); recorder.insertRight(offset, newContent); tree.commitUpdate(recorder);
// Delete a file tree.delete('src/app/old-file.ts');
return tree; };}Schema Definition
Section titled “Schema Definition”Define the options your schematic accepts in schema.json:
{ "$schema": "http://json-schema.org/schema", "$id": "MyFeatureSchema", "title": "Feature Schema", "type": "object", "properties": { "name": { "type": "string", "description": "The name of the feature", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What is the name of the feature?" }, "path": { "type": "string", "description": "The path to create the feature in", "default": "src/app/features" } }, "required": ["name"]}🏗️ Practical Example: Feature Scaffold
Section titled “🏗️ Practical Example: Feature Scaffold”Here’s a schematic that generates a complete feature scaffold:
File Templates
Section titled “File Templates”Create template files in src/feature/files/__name@dasherize__/:
__name@dasherize__.component.ts.template:
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
@Component({ selector: 'app-<%= dasherize(name) %>', templateUrl: './<%= dasherize(name) %>.component.html', styleUrl: './<%= dasherize(name) %>.component.scss', changeDetection: ChangeDetectionStrategy.OnPush,})export class <%= classify(name) %>Component { readonly items = signal<any[]>([]);}__name@dasherize__.service.ts.template:
import { Injectable, inject } from '@angular/core';import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root',})export class <%= classify(name) %>Service { private http = inject(HttpClient);
getAll() { return this.http.get<any[]>('/api/<%= dasherize(name) %>'); }}__name@dasherize__.routes.ts.template:
import { Routes } from '@angular/router';import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
export const <%= camelize(name) %>Routes: Routes = [ { path: '', component: <%= classify(name) %>Component },];Schematic Implementation
Section titled “Schematic Implementation”import { Rule, SchematicContext, Tree, apply, url, template, move, mergeWith,} from '@angular-devkit/schematics';import { strings } from '@angular-devkit/core';
export function feature(options: { name: string; path: string }): Rule { return (_tree: Tree, _context: SchematicContext) => { const templateSource = apply(url('./files'), [ template({ ...options, ...strings, // dasherize, classify, camelize, etc. }), move(options.path), ]);
return mergeWith(templateSource); };}Running the Custom Schematic
Section titled “Running the Custom Schematic”# Build the schematicnpm run build
# Run it (from an Angular project)schematics ./path-to-my-schematics:feature --name=user-management
# Or link it locally for ng generatenpm linkng generate my-schematics:feature user-managementOutput:
CREATE src/app/features/user-management/user-management.component.tsCREATE src/app/features/user-management/user-management.component.htmlCREATE src/app/features/user-management/user-management.component.scssCREATE src/app/features/user-management/user-management.service.tsCREATE src/app/features/user-management/user-management.routes.ts🧪 Testing Schematics
Section titled “🧪 Testing Schematics”Test your schematics using the SchematicTestRunner:
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';import * as path from 'path';
const collectionPath = path.join(__dirname, '../collection.json');
describe('feature schematic', () => { const runner = new SchematicTestRunner('my-schematics', collectionPath);
it('should create feature files', async () => { const tree = await runner.runSchematic('feature', { name: 'user-profile', path: 'src/app/features', });
expect(tree.files).toContain( '/src/app/features/user-profile/user-profile.component.ts' ); expect(tree.files).toContain( '/src/app/features/user-profile/user-profile.service.ts' ); expect(tree.files).toContain( '/src/app/features/user-profile/user-profile.routes.ts' ); });
it('should generate correct component content', async () => { const tree = await runner.runSchematic('feature', { name: 'user-profile', path: 'src/app/features', });
const content = tree.readContent( '/src/app/features/user-profile/user-profile.component.ts' ); expect(content).toContain('UserProfileComponent'); expect(content).toContain('ChangeDetectionStrategy.OnPush'); });});