Template processing system for generating files with dynamic content using popular template syntaxes and path transformations. The template engine supports both content templating (for file contents) and path templating (for file names and directory structures).
Main functions for applying templates to files and paths.
/**
* Applies both content and path templating to files
*/
function template<T>(options: T): Rule;
/**
* Applies templating to file contents only
*/
function contentTemplate<T>(options: T): Rule;
/**
* Applies templating to file paths only
*/
function pathTemplate<T>(options: T): Rule;
/**
* Applies templates to .template files and removes the extension
*/
function applyTemplates<T>(options: T): Rule;
/**
* Removes .template suffix from file names without processing content
*/
function renameTemplateFiles(): Rule;Usage Examples:
import {
template,
contentTemplate,
pathTemplate,
applyTemplates,
apply,
url,
move
} from "@angular-devkit/schematics";
interface ComponentOptions {
name: string;
path: string;
styleExt: string;
}
// Apply both content and path templates
function generateComponent(options: ComponentOptions): Rule {
return mergeWith(
apply(url('./files'), [
template({
...options,
// Template helper functions
classify: (str: string) => str.charAt(0).toUpperCase() + str.slice(1),
dasherize: (str: string) => str.replace(/[A-Z]/g, '-$&').toLowerCase(),
camelize: (str: string) => str.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
}),
move(options.path)
])
);
}
// Apply only content templates, keeping original file names
function generateConfig(options: any): Rule {
return mergeWith(
apply(url('./config-files'), [
contentTemplate(options),
move('/config')
])
);
}Lower-level operators for applying templates to individual files.
/**
* File operator for content templating
*/
function applyContentTemplate<T>(options: T): FileOperator;
/**
* File operator for path templating with advanced options
*/
function applyPathTemplate<T>(
data: T,
options?: PathTemplateOptions
): FileOperator;
interface PathTemplateOptions {
/** Interpolation delimiters (default: ['__', '__']) */
interpolationStart?: string;
interpolationEnd?: string;
/** Path segment separator handling */
normalize?: boolean;
}Usage Examples:
import {
applyContentTemplate,
applyPathTemplate,
forEach
} from "@angular-devkit/schematics";
// Manually apply templates using file operators
function customTemplateProcessing(options: any): Rule {
return forEach(
(entry) => {
// Apply content template first
let result = applyContentTemplate(options)(entry);
// Then apply path template with custom delimiters
if (result) {
result = applyPathTemplate(options, {
interpolationStart: '{{',
interpolationEnd: '}}',
normalize: true
})(result);
}
return result;
}
);
}The template engine uses EJS-style syntax for content templates and configurable delimiters for path templates.
Content Template Syntax:
// Template file content example
/*
import { Component } from '@angular/core';
@Component({
selector: '<%= dasherize(name) %>',
templateUrl: './<%= dasherize(name) %>.component.html',
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleExt %>']
})
export class <%= classify(name) %>Component {
title = '<%= name %>';
<% if (includeService) { %>
constructor(private service: <%= classify(name) %>Service) {}
<% } %>
}
*/Path Template Syntax:
// File path examples using default __ delimiters:
// __name@dasherize__.component.ts -> my-component.component.ts
// __path__/__name@classify__.service.ts -> src/app/MyComponent.service.ts
// Custom delimiter examples:
// {{name}}.component.ts -> my-component.component.ts (if using {{ }} delimiters)Common utility functions used within templates for string transformations.
// These are typically provided in the template options object
interface TemplateHelpers {
/** Convert string to PascalCase */
classify: (str: string) => string;
/** Convert string to kebab-case */
dasherize: (str: string) => string;
/** Convert string to camelCase */
camelize: (str: string) => string;
/** Convert string to UPPER_CASE */
underscore: (str: string) => string;
/** Capitalize first letter */
capitalize: (str: string) => string;
/** Convert to lowercase */
decamelize: (str: string) => string;
}Usage Examples:
import { strings } from "@angular-devkit/core";
// Using built-in string utilities
function generateWithHelpers(options: any): Rule {
return apply(url('./files'), [
template({
...options,
// Import and use core string utilities
...strings,
// Custom helpers
plural: (str: string) => str + 's',
singular: (str: string) => str.endsWith('s') ? str.slice(0, -1) : str
})
]);
}Conditional Templates:
Template files can contain conditional logic:
/*
// component.ts.template
import { Component<% if (standalone) { %>, Input<% } %> } from '@angular/core';
<% if (includeInterface) { %>
interface <%= classify(name) %>Data {
id: number;
name: string;
}
<% } %>
@Component({
selector: '<%= selector %>',
<% if (standalone) { %>standalone: true,<% } %>
templateUrl: './<%= dasherize(name) %>.component.html'
})
export class <%= classify(name) %>Component {
<% if (includeInterface) { %>
data: <%= classify(name) %>Data[] = [];
<% } %>
}
*/Loop Templates:
/*
// service.ts.template
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
<% methods.forEach(method => { %>
<%= method.name %>(<% method.params.forEach((param, index) => { %><%= param.name %>: <%= param.type %><% if (index < method.params.length - 1) { %>, <% } %><% }); %>): <%= method.returnType %> {
// TODO: implement <%= method.name %>
<% if (method.returnType !== 'void') { %>return null as any;<% } %>
}
<% }); %>
}
*/Complex Template Options:
interface ComplexTemplateOptions {
name: string;
path: string;
standalone: boolean;
includeInterface: boolean;
includeService: boolean;
methods: Array<{
name: string;
params: Array<{ name: string; type: string }>;
returnType: string;
}>;
imports: string[];
dependencies: string[];
}
function generateComplexComponent(options: ComplexTemplateOptions): Rule {
return mergeWith(
apply(url('./complex-files'), [
template({
...options,
// String transformation helpers
classify: strings.classify,
dasherize: strings.dasherize,
camelize: strings.camelize,
// Custom helpers
hasMethod: (methodName: string) =>
options.methods.some(m => m.name === methodName),
getImportPath: (dep: string) =>
dep.startsWith('@') ? dep : `./${dep}`,
// Date helpers
currentYear: new Date().getFullYear(),
timestamp: new Date().toISOString()
}),
move(options.path)
])
);
}File Naming Conventions:
files/
├── __name@dasherize__.component.ts.template
├── __name@dasherize__.component.html.template
├── __name@dasherize__.component.__styleExt__.template
├── __name@dasherize__.component.spec.ts.template
└── index.ts.templateDirectory Structure Templates:
files/
├── __path__/
│ ├── __name@dasherize__/
│ │ ├── __name@dasherize__.component.ts.template
│ │ └── __name@dasherize__.component.html.template
│ └── shared/
│ └── __name@dasherize__.interface.ts.templateException types thrown by the template engine.
/**
* Thrown when a required template option is not defined
*/
class OptionIsNotDefinedException extends SchematicsException {
constructor(name: string);
}
/**
* Thrown when an unknown template pipe is used
*/
class UnknownPipeException extends SchematicsException {
constructor(name: string);
}
/**
* Thrown when a template pipe is used incorrectly
*/
class InvalidPipeException extends SchematicsException {
constructor(name: string);
}Organizing Template Options:
interface BaseTemplateOptions {
name: string;
path: string;
}
interface ComponentTemplateOptions extends BaseTemplateOptions {
selector?: string;
standalone?: boolean;
styleExt?: 'css' | 'scss' | 'sass' | 'less';
includeSpec?: boolean;
}
function createComponentTemplate(options: ComponentTemplateOptions): Rule {
// Provide defaults
const templateOptions = {
selector: `app-${strings.dasherize(options.name)}`,
standalone: false,
styleExt: 'css' as const,
includeSpec: true,
...options,
// Always include string helpers
...strings
};
return mergeWith(
apply(url('./component-files'), [
template(templateOptions),
move(options.path)
])
);
}type FileOperator = (entry: FileEntry) => FileEntry | null;
interface PathTemplateOptions {
interpolationStart?: string;
interpolationEnd?: string;
normalize?: boolean;
}
interface TemplateOptions {
[key: string]: any;
}