> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# Angular Schematics

> Create custom code generators and transformations with Angular Schematics

## 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.

<Info>
  Think of schematics as "code recipes" - they describe how to generate or transform code in a repeatable, configurable way.
</Info>

## 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.

```mermaid theme={null}
graph LR
    A[Input Tree] --> B[Schematic Rules]
    B --> C[Transformed Tree]
    C --> D[Commit to Disk]
```

<CardGroup cols={2}>
  <Card title="Tree" icon="tree">
    Virtual file system representing project structure
  </Card>

  <Card title="Rule" icon="scale-balanced">
    Transformation applied to the tree
  </Card>

  <Card title="Template" icon="file-code">
    File template with placeholders
  </Card>

  <Card title="Action" icon="bolt">
    Create, update, delete, or rename operations
  </Card>
</CardGroup>

## Official Schematics

Angular provides built-in schematics for common tasks:

### Code Generation Schematics

<Tabs>
  <Tab title="Component">
    ```bash theme={null}
    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
  </Tab>

  <Tab title="Service">
    ```bash theme={null}
    ng generate @schematics/angular:service auth
    ```

    Generates:

    * Service TypeScript file
    * Service test file

    **Options:**

    * `--skip-tests`: Skip test file
    * `--flat`: Don't create folder
  </Tab>

  <Tab title="Module">
    ```bash theme={null}
    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
  </Tab>

  <Tab title="Directive">
    ```bash theme={null}
    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
  </Tab>

  <Tab title="Pipe">
    ```bash theme={null}
    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
  </Tab>

  <Tab title="Guard">
    ```bash theme={null}
    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
  </Tab>
</Tabs>

### Migration Schematics

Angular provides powerful migration schematics:

<AccordionGroup>
  <Accordion title="Standalone Migration">
    Convert components, directives, and pipes to standalone:

    ```bash theme={null}
    ng generate @angular/core:standalone
    ```

    **Three-phase migration:**

    <Steps>
      <Step title="Convert to Standalone">
        Convert declarations to standalone and update imports
      </Step>

      <Step title="Remove NgModules">
        Delete unnecessary NgModule classes
      </Step>

      <Step title="Update Bootstrap">
        Switch to `bootstrapApplication` API
      </Step>
    </Steps>

    **Options:**

    * `mode`: Migration mode (convert, prune, bootstrap)
    * `path`: Relative path to migrate

    See the [standalone migration README](https://github.com/angular/angular/blob/main/packages/core/schematics/ng-generate/standalone-migration/README.md) for details.
  </Accordion>

  <Accordion title="Control Flow Migration">
    Migrate to new control flow syntax:

    ```bash theme={null}
    ng generate @angular/core:control-flow-migration
    ```

    **Transforms:**

    * `*ngIf` → `@if`
    * `*ngFor` → `@for`
    * `*ngSwitch` → `@switch`

    **Before:**

    ```html theme={null}
    <div *ngIf="show">Content</div>
    <li *ngFor="let item of items">{{ item }}</li>
    ```

    **After:**

    ```html theme={null}
    @if (show) {
      <div>Content</div>
    }
    @for (item of items; track item) {
      <li>{{ item }}</li>
    }
    ```
  </Accordion>

  <Accordion title="Signal Migrations">
    Migrate to signal-based APIs:

    **Signal Inputs:**

    ```bash theme={null}
    ng generate @angular/core:signal-input-migration
    ```

    **Signal Queries:**

    ```bash theme={null}
    ng generate @angular/core:signal-queries-migration
    ```

    **Output Migration:**

    ```bash theme={null}
    ng generate @angular/core:output-migration
    ```
  </Accordion>

  <Accordion title="Style Migrations">
    **NgStyle to Style:**

    ```bash theme={null}
    ng generate @angular/core:ngstyle-to-style-migration
    ```

    **NgClass to Class:**

    ```bash theme={null}
    ng generate @angular/core:ngclass-to-class-migration
    ```
  </Accordion>

  <Accordion title="Other Migrations">
    **Inject Migration:**

    ```bash theme={null}
    ng generate @angular/core:inject-migration
    ```

    **Router Testing Module:**

    ```bash theme={null}
    ng generate @angular/core:router-testing-module-migration
    ```

    **Cleanup Unused Imports:**

    ```bash theme={null}
    ng generate @angular/core:cleanup-unused-imports
    ```
  </Accordion>
</AccordionGroup>

<Note>
  All migration schematics support a `--path` option to migrate specific directories.
</Note>

***

## Creating Custom Schematics

Build your own code generators for common patterns in your application.

### Setup

<Steps>
  <Step title="Install Schematics CLI">
    ```bash theme={null}
    npm install -g @angular-devkit/schematics-cli
    ```
  </Step>

  <Step title="Create Schematics Collection">
    ```bash theme={null}
    schematics blank my-schematics
    cd my-schematics
    npm install
    ```
  </Step>

  <Step title="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
    ```
  </Step>
</Steps>

### Define Schema

**schema.json:**

```json theme={null}
{
  "$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:**

```typescript theme={null}
export interface Schema {
  name: string;
  path?: string;
  project?: string;
  export?: boolean;
}
```

### Implement Schematic

**index.ts:**

```typescript theme={null}
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](mailto:_name@dasherize__.component.ts):**

```typescript theme={null}
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](mailto:_name@dasherize__.component.html):**

```html theme={null}
<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:

<ResponseField name="String Functions">
  | Function     | Input          | Output         | Description             |
  | ------------ | -------------- | -------------- | ----------------------- |
  | `dasherize`  | `MyComponent`  | `my-component` | Convert to kebab-case   |
  | `classify`   | `my-component` | `MyComponent`  | Convert to PascalCase   |
  | `camelize`   | `my-component` | `myComponent`  | Convert to camelCase    |
  | `capitalize` | `hello`        | `Hello`        | Capitalize first letter |
  | `underscore` | `MyComponent`  | `my_component` | Convert to snake\_case  |
</ResponseField>

***

## Schematic Rules

Rules define transformations applied to the tree:

### Basic Rules

```typescript theme={null}
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

```typescript theme={null}
import { chain, Rule } from '@angular-devkit/schematics';

export function mySchematic(): Rule {
  return chain([
    createFile(),
    updateFile(),
    deleteFile(),
  ]);
}
```

### Conditional Rules

```typescript theme={null}
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:

```typescript theme={null}
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,
    "\nimport { 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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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

<Steps>
  <Step title="Build the Package">
    ```bash theme={null}
    npm run build
    ```
  </Step>

  <Step title="Test Locally">
    ```bash theme={null}
    npm link
    cd /path/to/test-project
    npm link my-schematics
    ng generate my-schematics:my-schematic test
    ```
  </Step>

  <Step title="Publish to npm">
    ```bash theme={null}
    npm publish
    ```
  </Step>

  <Step title="Use in Projects">
    ```bash theme={null}
    ng add my-schematics
    ng generate my-schematics:my-schematic test
    ```
  </Step>
</Steps>

***

## Collection Configuration

**collection.json:**

```json theme={null}
{
  "$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"
    }
  }
}
```

<ParamField path="factory" type="string" required>
  Path to schematic implementation and export name
</ParamField>

<ParamField path="schema" type="string">
  Path to JSON schema file defining options
</ParamField>

<ParamField path="aliases" type="string[]">
  Alternative names for the schematic
</ParamField>

<ParamField path="hidden" type="boolean">
  Hide from `ng generate` list
</ParamField>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Validation">
    Always validate options and check for existing files:

    ```typescript theme={null}
    if (!options.name) {
      throw new SchematicsException('Name is required');
    }

    if (tree.exists(targetPath)) {
      throw new SchematicsException(`File already exists: ${targetPath}`);
    }
    ```
  </Accordion>

  <Accordion title="Logging">
    Provide helpful feedback:

    ```typescript theme={null}
    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');
    ```
  </Accordion>

  <Accordion title="Dry Run">
    Test with `--dry-run` flag:

    ```bash theme={null}
    ng generate my-schematics:my-schematic test --dry-run
    ```

    Shows what would be generated without creating files.
  </Accordion>

  <Accordion title="Path Handling">
    Use `normalize` and `join` for cross-platform compatibility:

    ```typescript theme={null}
    import { normalize, join } from '@angular-devkit/core';

    const targetPath = normalize(join(options.path, options.name));
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="CLI Overview" icon="terminal" href="/cli/overview">
    Return to CLI overview
  </Card>

  <Card title="Builders" icon="hammer" href="/cli/builders">
    Learn about custom builders
  </Card>
</CardGroup>

## Resources

<CardGroup cols={2}>
  <Card title="Schematics Guide" icon="book" href="https://angular.dev/tools/cli/schematics">
    Official schematics documentation
  </Card>

  <Card title="Angular Schematics" icon="github" href="https://github.com/angular/angular-cli/tree/main/packages/schematics/angular">
    Official Angular schematics source
  </Card>

  <Card title="DevKit Schematics" icon="code" href="https://github.com/angular/angular-cli/tree/main/packages/angular_devkit/schematics">
    Schematics DevKit source
  </Card>

  <Card title="Example Schematics" icon="flask" href="https://github.com/angular/angular-cli/tree/main/packages/core/schematics">
    Angular Core migration schematics
  </Card>
</CardGroup>
