- Published on
Interpolating Schematic files and filenames
- Authors
- Name
- Kevin Schuchard
- @KevinSchuchard
Adding files
When adding template files there are generally three common steps to take. Let's assume the following directory of template files that our example schematic will use.
files
├── __name@dasherize__.component.__style__
├── __name@dasherize__.component.html
├── __name@dasherize__.component.spec.ts
└── __name@dasherize__.component.ts
- Use the
url
method to get aSource
. This is a relative path to the ./files folder containing our template files above. - Then apply the first of two
Rule
's with thetemplate
method. This provides the values and methods to be used or interpolated in the files and filenames. - Lastly, apply the second
Rule
tomove
theSource
to the root of the host application that the schematic is running against. This is everything in the ./files directory above. In this example, we're hard-coding the path inmove('./')
but this could also be dynamically set.
function addFiles(options: Options): Rule {
return (tree: Tree, context: SchematicContext) => {
const templateSource = apply(url('./files'), [
template({
...options,
}),
move('./'),
]);
return chain([mergeWith(templateSource)])(tree, context);
};
}
Adding custom template values
If you want to provide your own values or methods you can merge them into the template
method Rule
. These could be custom values, helper functions or conditional variables for decision logic inside the template files and filenames.
const templateSource = apply(url('./files'), [
template({
...options,
...{ENV_1: 'some-value'},
'if-flat': (s: string) => options.flat ? '' : s,
}),
move('./'),
]);
Using values in files
Schematics use a similar interpolation concept to Angular in that special characters wrap a value or method and interpolate it. In Angular, we accomplish this like so.
<h1>{{ name }}</h1>
In schematic template files it's slightly different with <%=
and %>
.
<h1><%= name %></h1>
Note that the spacing inside the wrapping elements doesn't matter after the file is interpolated. If name = 'Michael Scott'
you'll end up with.
<h1>Michael Scott</h1>
Methods can also be used if they've been provided as mentioned above with the template
method.
<h1><%= uppercase(name) %></h1>
Using values in filenames
Filenames operate similarly in how they interpolate both values, methods, and conditionals. The main difference is how you signal what part of the filename should be interpolated. In filenames, you use a double underscore to wrap values and methods.
__name__.component.ts
As you might expect, the __name__
will be interpolated to the match value provided from the template
method.
Methods are also accessible in the filenames and are called with the @
symbol.
__name@dasherize__.component.ts
Here the name
value will be used in the dasherize
function and returned. If name = 'MyApp'
then the above would produce something like my-app.component.ts
Conditional template logic
Templates can use values provided in the template
method to make logical decisions. This could look like deciding when to add or remove code inside the templates. Let's look at a snippet from the Angular component schematic template file. At first glance, this may look like a jumbled mess. However, if you look closely you'll see the familiar wrapping elements from before, <%=
and %>
.
@Component({
selector: '<%= selector %>',<% if(inlineTemplate) { %>
template: `
<p>
<%= dasherize(name) %> works!
</p>
`,<% } else { %>
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
styles: []<% } else { %>
styleUrls: ['./<%= dasherize(name) %>.component.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>,
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
While this template file as a whole is not executable in its current form, you can think of the code between the <%=
and %>
elements as being so. So if we look at the style conditionals we can see that it's a basic if
/ else
block.
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
styles: []<% } else { %>
styleUrls: ['./<%= dasherize(name) %>.component.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>,
if
theinlineStyle
value is truthy, it prints the styles Array property,styles: []
, whose closing bracket is on the next lineelse
it prints thestylesUrls
property and interpolates thedasherize
d filename
andstyle
file extension.
Conditional file logic
If you've run the Angular CLI schematics you've likely seen an option called --flat
, which usually is a "Flag to indicate if a dir is created". This is useful when you want to add a conditional check to the creation of a directory. If we provide a template
method called if-flat
, it can be used in the filename. In this example, it will either use the folder it's applied on to create a directory or add the directory contents without the containing folder.
template({
...options,
'if-flat': (s: string) => options.flat ? '' : s,
}),
Once that method is available it can be used on a directory.
files/__name@dasherize@if-flat__
Test this functionality out by creating an Angular CLI component schematic with different --flat
options
ng g c flatA --flat=false
ng g c flatB --flat=true
Resources
- The Angular CLI component schematic uses all of the things discussed in this article.
- Check out my Schematic Sandbox for rapidly developing schematics.