- 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
urlmethod 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 thetemplatemethod. This provides the values and methods to be used or interpolated in the files and filenames. - Lastly, apply the second
RuletomovetheSourceto 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) { %>,
iftheinlineStylevalue is truthy, it prints the styles Array property,styles: [], whose closing bracket is on the next lineelseit prints thestylesUrlsproperty and interpolates thedasherized filenameandstylefile 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.