When building my components I always copy and paste a comment at the top of my files to itemize all the specs and user stories as I think and build them out, something like as follows
/*
Some Component Name
PURPOSE :
VIEWS :
ELEMENTS :
INTERACTIVITY :
GETS DATA FROM :
SENDS DATA TO :
USER STORIES :
*/
I've been wanting to dive deeper into the capabilities of NX so I figured a good introduction would be to make my own schematics for components, services, directives etc. because I have slightly different things I like to list like for example, why would I want to itemize views
and elements
in a service file or list interactions with a back end in a component file?
I successfully generated a plugin library in my libs
folder by running
nx g @nx/workspace:plugin libs/my-plugin
Then I created a new generator by running
nx g @nx/workspace:generator libs/my-plugin/src/generators/my-component-generator/my-component-generator
Once that completed I had to manually create a component
folder and manually create the files I want to generate using some type of projection syntax. The file names are
// I'm using a code container here because the __ just makes the text bold
// in the post.
__name@dasherize__.component.ts
__name@dasherize__.component.spec.ts
__name@dasherize__.component.html
__name@dasherize__.component.css
So far I only tried defining the component.ts
file which looks like this
import { Component } from '@angular/core';
@Component({
selector : 'app-__name@dasherize__',
templateUrl : './__name@dasherize__.component.html',
styleUrls : [ './__name@dasherize__.component.css' ]
})
export class <%= classify(name) %>Component {}
The <%= classify(name) %>
part is causing a ts 1139
error with the <%= %>
part and saying name is deprecated
on the name
part. I tried looking deeper into how to do this and saw another method using __name@classify__
and got an error saying declaration expected
. I came across other examples where instead of using __name@dasherize__
they used <%= dasherize(name) %>
In my index.ts.template
file I have this
const variable = "<%= name %>";
so I tried doing
export class <%= name =>Component {}
and I'm still getting a red squiggly line. Can someone help me figure out how to get this working?
Ok after a lot of tinkering and experimenting I learned that the <%= %>
thing comes from EJS..... which I had never heard of until figuring this out to in turn understand how to use it. The solution I eventually came to goes as follows
1. Modify the name of my schematic files to use the following syntax
__name__.component.css.template
__name__.component.html.template
__name__.component.ts.template
__name__.component.spec.ts.template
2. Define the options I want to provide in the CLI using the interface inside my schema.d.ts
file
/*
this allows you to use
--name=some-name --project=some-project --location=path/to/somewhere
in the CLI or if you don't want to add them as flags you'll be prompted
with questions to answer after you run your command. You can define any
type of property you want to suit whatever you want to be able to define
as a flag or or question to answer.
*/
export interface CustomComponentGeneratorSchema {
name : string;
project : string;
location : string;
}
3. Configure the options defined in the CustomComponentGeneratorSchema
in the "properties"
object of the schema.json
file
{
"$schema" : "...",
"$id" : "...",
"title" : "...",
"type" : "...",
"properties" : {
"name" : {
"type" : "string"
"description" : "your description",
"$default" : {
"$source" : "argv",
"index" : 0
},
"x-prompt" : "name your component"
},
"project" : {
"type" : "string",
"description : "your description",
"x-prompt" : "what project would you like to add this to?",
},
"location" : {
"type" : "string",
"description" : "your description",
"x-prompt" : "where do you want this located?"
}
},
"required" : ["name", "project"]
}
3. Modify the main generator
file to handle your schema
options.
import { CustomComponentGeneratorSchema as Schema } from "./schema";
import { /*stuff*/ names, getProjects } from "@nx/devkit";
import { strings } from '@angular-devkit/core';
import * as path from 'path';
export async function myCustomComponentGenerator(tree : Tree, options : Schema){
// the names method returns camel case, pascal case,
// kebab case, etc. versions of the name you enter in
// the --name flag
const componentNames = names( options.name );
// getProjects() fetces all the projects in the workspace
const projects = getProjects( tree );
// uses the --project property to search projects for
// the target project
const targetProject = projects.get( options.project );
if ( !targetProject ) {
// throw an error if the project isn't found
throw new Error(`Project "${options.project}" not found.`);
}
// generates a file path for the component files
const targetPath = path.join(
// the target project's root directory
targetProject.root,
// the value passed into the --location prop
options.location,
// fileName() returns the "kebab case" version of
// the --name passed in.
componentNames.fileName
);
// generates the files for the custom component
generateFiles(
tree,
path.join(__dirname, 'files'),
targetPath,
{ ...componentNames, tmpl : '' }
);
await formatFiles(tree);
}
4. Pass in the appropriate value returned by the names() method into the scematic files using EJS syntax
__name__component.ts.template
/*
<%= className %>
PURPOSE :
VIEWS :
ELEMENTS :
INTERACTIVITY :
GETS DATA FROM :
SENDS DATA TO :
USER STORIES :
*/
import { Component } from '@angular/core';
@Component({
selector : 'lib-<%= fileName %>',
templateUrl : './<%= fileName %>.component.html',
styleUrls : [ './<%= fileName %>.component.css' ]
})
export class <%= className %>Component {}
name.component.spec.ts.template
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { <%= className %>Component } from './<%= fileName %>.component';
describe('<%= className %>Component', () => {
let component : <%= className %>Component;
let fixture : ComponentFixture<<%= className %>Component>;
beforeEach( async () => {
await TestBed.configureTestingModule( {
imports : [ <%= className %>Component ],
} ).compileComponents();
fixture = TestBed.createComponent( <%= className %>Component );
component = fixture.componentInstance;
fixture.detectChanges();
});
it( 'should create' , () => {
expect( component ).toBeTruthy();
});
});
name.component.html.template
<p><%= fileName %> works</p>
Once that's complete we can just run the nx command in our CLI and everything will generate as expected.