Composable transformation functions that modify trees in a functional, predictable way. Rules are the building blocks of schematics, allowing for complex file system transformations through simple, composable functions.
The fundamental types that power the rule system.
/**
* A function that transforms a tree, can be sync or async
*/
type Rule = (tree: Tree, context: SchematicContext) =>
Tree | Observable<Tree> | Rule | Promise<void | Tree | Rule> | void;
/**
* A function that creates a tree from context
*/
type Source = (context: SchematicContext) => Tree | Observable<Tree>;
/**
* Factory function that creates rules from options
*/
type RuleFactory<T> = (options: T) => Rule;
/**
* Function that operates on individual files
*/
type FileOperator = (entry: FileEntry) => FileEntry | null;Core functions for creating and combining rules.
/**
* Creates a source that returns the given tree
*/
function source(tree: Tree): Source;
/**
* Creates a source that returns an empty tree
*/
function empty(): Source;
/**
* Returns a rule that does nothing (identity function)
*/
function noop(): Rule;
/**
* Chains multiple rules into a single rule that executes sequentially
*/
function chain(rules: Iterable<Rule> | AsyncIterable<Rule>): Rule;
/**
* Applies multiple rules to a source and returns the transformed source
*/
function apply(source: Source, rules: Rule[]): Source;
/**
* Merges input tree with the source tree using specified strategy
*/
function mergeWith(source: Source, strategy?: MergeStrategy): Rule;Usage Examples:
import {
Rule,
chain,
apply,
mergeWith,
empty,
noop,
Tree,
SchematicContext
} from "@angular-devkit/schematics";
// Simple rule that creates a file
function createReadme(): Rule {
return (tree: Tree, context: SchematicContext) => {
tree.create('/README.md', '# My Project\n\nThis is a generated project.');
return tree;
};
}
// Chain multiple rules together
function setupProject(): Rule {
return chain([
createReadme(),
createPackageJson(),
createGitignore()
]);
}
// Apply rules to a source
function generateFromTemplate(templateUrl: string, options: any): Rule {
return mergeWith(
apply(url(templateUrl), [
template(options),
move('/src')
])
);
}Rules for filtering and transforming files within trees.
/**
* Filters tree files based on predicate function
*/
function filter(predicate: FilePredicate<boolean>): Rule;
/**
* Applies file operator to each file in the tree
*/
function forEach(operator: FileOperator): Rule;
/**
* Conditionally applies file operator based on predicate
*/
function when(predicate: FilePredicate<boolean>, operator: FileOperator): FileOperator;
/**
* Executes rule on a branch and merges back to original tree
*/
function branchAndMerge(rule: Rule, strategy?: MergeStrategy): Rule;
/**
* Partitions tree, applies different rules to each partition, then merges
*/
function partitionApplyMerge(
predicate: FilePredicate<boolean>,
ruleYes: Rule,
ruleNo?: Rule
): Rule;
/**
* Composes multiple file operators into a single operator
*/
function composeFileOperators(operators: FileOperator[]): FileOperator;
/**
* Applies rules to a subtree at the specified path
*/
function applyToSubtree(path: string, rules: Rule[]): Rule;Usage Examples:
import {
filter,
forEach,
when,
branchAndMerge,
partitionApplyMerge
} from "@angular-devkit/schematics";
// Filter only TypeScript files
const onlyTsFiles = filter((path) => path.endsWith('.ts'));
// Transform all files with uppercase content
const uppercaseContent = forEach((entry) => {
return {
...entry,
content: Buffer.from(entry.content.toString().toUpperCase())
};
});
// Conditionally modify files
const addHeader = when(
(path) => path.endsWith('.ts'),
(entry) => ({
...entry,
content: Buffer.from(`// Generated file\n${entry.content.toString()}`)
})
);
// Work on a branch to avoid conflicts
const safeTransform = branchAndMerge(
chain([
filter((path) => path.includes('/src/')),
forEach(addHeader)
])
);Rules for moving and organizing files within the tree.
/**
* Moves files or directories from one path to another
* @param from - Source path pattern or specific path
* @param to - Destination path (optional, defaults to root)
*/
function move(from: string, to?: string): Rule;Usage Examples:
import { move } from "@angular-devkit/schematics";
// Move all files from /temp to /src
const moveToSrc = move('/temp', '/src');
// Move files with a specific pattern
const moveComponents = move('/components', '/src/app/components');Rules for executing other schematics within the current schematic.
/**
* Runs a schematic from the same collection
*/
function schematic<OptionT>(
schematicName: string,
options: OptionT,
executionOptions?: Partial<ExecutionOptions>
): Rule;
/**
* Runs a schematic from a different collection
*/
function externalSchematic<OptionT>(
collectionName: string,
schematicName: string,
options: OptionT,
executionOptions?: Partial<ExecutionOptions>
): Rule;Usage Examples:
import { schematic, externalSchematic } from "@angular-devkit/schematics";
// Run another schematic from same collection
function generateWithComponent(options: any): Rule {
return chain([
schematic('component', {
name: options.componentName,
path: options.path
}),
// Additional rules...
]);
}
// Run schematic from external collection
function generateWithAngularMaterial(): Rule {
return externalSchematic('@angular/material', 'nav', {
name: 'main-nav'
});
}Rules for creating sources from external URLs and local file systems.
/**
* Creates a source from a URL string (file://, http://, etc.)
*/
function url(urlString: string): Source;Usage Examples:
import { url, apply, template, move } from "@angular-devkit/schematics";
// Load templates from local file system
function generateFromLocalTemplate(options: any): Rule {
return mergeWith(
apply(url('./files'), [
template({
...options,
classify: (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
}),
move(options.path || '/')
])
);
}
// Load from HTTP URL (if supported by host)
function generateFromRemoteTemplate(options: any): Rule {
return mergeWith(
apply(url('https://example.com/templates'), [
template(options)
])
);
}Utilities for executing rules and sources with proper error handling.
/**
* Calls a source function and returns observable tree
*/
function callSource(source: Source, context: SchematicContext): Observable<Tree>;
/**
* Calls a rule function and returns observable tree
*/
function callRule(
rule: Rule,
input: Tree | Observable<Tree>,
context: SchematicContext
): Observable<Tree>;Utility for generating random test files.
/**
* Creates a source with randomly generated files for testing
*/
function random(options: RandomOptions): Source;
interface RandomOptions {
/** Number of files to generate */
count?: number;
/** File content options */
content?: {
/** Minimum content length */
minLength?: number;
/** Maximum content length */
maxLength?: number;
};
/** Path generation options */
path?: {
/** Path depth range */
depth?: [number, number];
/** File extension options */
extensions?: string[];
};
}Exception types that can be thrown during rule execution.
/**
* Thrown when a rule returns an invalid result
*/
class InvalidRuleResultException extends SchematicsException {
constructor(result?: any);
}
/**
* Thrown when a source returns an invalid result
*/
class InvalidSourceResultException extends SchematicsException {
constructor(result?: any);
}Complex Rule Composition:
import {
Rule,
chain,
apply,
mergeWith,
url,
template,
move,
filter,
forEach,
when,
branchAndMerge
} from "@angular-devkit/schematics";
function complexSchematic(options: ComplexOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
// Conditional logic in rules
const rules: Rule[] = [];
if (options.generateComponent) {
rules.push(
mergeWith(
apply(url('./component-files'), [
template(options),
move(options.componentPath)
])
)
);
}
if (options.generateService) {
rules.push(
mergeWith(
apply(url('./service-files'), [
template(options),
move(options.servicePath)
])
)
);
}
// Process existing files
rules.push(
branchAndMerge(
chain([
filter((path) => path.endsWith('.ts')),
forEach(
when(
(path) => path.includes('/src/'),
(entry) => ({
...entry,
content: Buffer.from(
addImports(entry.content.toString(), options.imports)
)
})
)
)
])
)
);
return chain(rules)(tree, context);
};
}interface ExecutionOptions {
interactive: boolean;
dryRun: boolean;
force: boolean;
defaults: boolean;
}
type TaskId = symbol;
interface TaskConfigurationGenerator<T> {
name: string;
options: T;
}