Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(angular): support generating artifacts using options as provided #19527

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions docs/generated/packages/angular/generators/component.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
{
"name": "component",
"factory": "./src/generators/component/component",
"factory": "./src/generators/component/component#componentGeneratorInternal",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "SchematicsAngularComponent",
"title": "Angular Component Schema",
"cli": "nx",
"type": "object",
"description": "Creates a new, generic Angular component definition in the given or default project.",
"description": "Creates a new Angular component.",
"additionalProperties": false,
"properties": {
"path": {
"name": {
"type": "string",
"description": "The name of the component.",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the component?"
},
"directory": {
"type": "string",
"description": "The directory at which to create the component file. When `--nameAndDirectoryFormat=as-provided`, it will be relative to the current working directory. Otherwise, it will be relative to the workspace root.",
"aliases": ["dir", "path"],
"x-priority": "important"
},
"nameAndDirectoryFormat": {
"description": "Whether to generate the component in the directory as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
"type": "string",
"format": "path",
"description": "The path at which to create the component file, relative to the current workspace. Default is a folder with the same name as the component in the project root.",
"visible": false
"enum": ["as-provided", "derived"]
},
"project": {
"type": "string",
"description": "The name of the project.",
"$default": { "$source": "projectName" },
"x-dropdown": "projects"
},
"name": {
"type": "string",
"description": "The name of the component.",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the component?"
"x-dropdown": "projects",
"x-deprecated": "Provide the `directory` option instead and use the `as-provided` format. The project will be determined from the directory provided. It will be removed in Nx v18."
},
"prefix": {
"type": "string",
Expand Down Expand Up @@ -89,7 +95,8 @@
"flat": {
"type": "boolean",
"description": "Create the new files at the top level of the current project.",
"default": false
"default": false,
"x-deprecated": "Provide the `directory` option instead and use the `as-provided` format. It will be removed in Nx v18."
},
"skipImport": {
"type": "boolean",
Expand Down Expand Up @@ -124,13 +131,13 @@
"x-priority": "internal"
}
},
"required": ["name", "project"],
"required": ["name"],
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Component\" %}\n\nCreate a component named `my-component`:\n\n```bash\nnx g @nx/angular:component my-component\n```\n\n{% /tab %}\n\n{% tab label=\"Standalone Component\" %}\n\nCreate a standalone component named `my-component`:\n\n```bash\nnx g @nx/angular:component my-component --standalone\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Component\" %}\n\nCreate a component named `my-component` with inline styles and inline template:\n\n```bash\nnx g @nx/angular:component my-component --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Component with OnPush Change Detection Strategy\" %}\n\nCreate a component named `my-component` with OnPush Change Detection Strategy:\n\n```bash\nnx g @nx/angular:component my-component --changeDetection=OnPush\n```\n\n{% /tab %}\n",
"presets": []
},
"aliases": ["c"],
"description": "Generate an Angular Component.",
"implementation": "/packages/angular/src/generators/component/component.ts",
"implementation": "/packages/angular/src/generators/component/component#componentGeneratorInternal.ts",
"hidden": false,
"path": "/packages/angular/src/generators/component/schema.json",
"type": "generator"
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
"description": "Creates an Angular application."
},
"component": {
"factory": "./src/generators/component/component",
"factory": "./src/generators/component/component#componentGeneratorInternal",
"schema": "./src/generators/component/schema.json",
"aliases": ["c"],
"description": "Generate an Angular Component."
Expand Down
38 changes: 22 additions & 16 deletions packages/angular/src/generators/component/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,27 @@ import {
import type { Schema } from './schema';

export async function componentGenerator(tree: Tree, rawOptions: Schema) {
validateOptions(tree, rawOptions);
const options = normalizeOptions(tree, rawOptions);
await componentGeneratorInternal(tree, {
nameAndDirectoryFormat: 'derived',
...rawOptions,
});
}

const componentNames = names(options.name);
const typeNames = names(options.type);
export async function componentGeneratorInternal(
tree: Tree,
rawOptions: Schema
) {
validateOptions(tree, rawOptions);
const options = await normalizeOptions(tree, rawOptions);

generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
options.directory,
{
fileName: componentNames.fileName,
className: componentNames.className,
type: typeNames.fileName,
typeClassName: typeNames.className,
name: options.name,
fileName: options.fileName,
symbolName: options.symbolName,
style: options.style,
inlineStyle: options.inlineStyle,
inlineTemplate: options.inlineTemplate,
Expand All @@ -46,7 +52,7 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
if (options.skipTests) {
const pathToSpecFile = joinPathFragments(
options.directory,
`${componentNames.fileName}.${typeNames.fileName}.spec.ts`
`${options.fileName}.spec.ts`
);

tree.delete(pathToSpecFile);
Expand All @@ -55,7 +61,7 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
if (options.inlineTemplate) {
const pathToTemplateFile = joinPathFragments(
options.directory,
`${componentNames.fileName}.${typeNames.fileName}.html`
`${options.fileName}.html`
);

tree.delete(pathToTemplateFile);
Expand All @@ -64,7 +70,7 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
if (options.style === 'none' || options.inlineStyle) {
const pathToStyleFile = joinPathFragments(
options.directory,
`${componentNames.fileName}.${typeNames.fileName}.${options.style}`
`${options.fileName}.${options.style}`
);

tree.delete(pathToStyleFile);
Expand All @@ -78,13 +84,13 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
);
addToNgModule(
tree,
options.path,
options.directory,
modulePath,
componentNames.fileName,
`${componentNames.className}${typeNames.className}`,
`${componentNames.fileName}.${typeNames.fileName}`,
options.name,
options.symbolName,
options.fileName,
'declarations',
options.flat,
true,
options.export
);
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p><%= name %> works!</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { <%= symbolName %> } from './<%= fileName %>';

describe('<%= symbolName %>', () => {
let component: <%= symbolName %>;
let fixture: ComponentFixture<<%= symbolName %>>;

beforeEach(async () => {
await TestBed.configureTestingModule({
<%= standalone ? 'imports' : 'declarations' %>: [ <%= symbolName %> ]
})
.compileComponents();

fixture = TestBed.createComponent(<%= symbolName %>);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { CommonModule } from '@angular/common';<% } %>
selector: '<%= selector %>',<%}%><% if(standalone) {%>
standalone: true,
imports: [CommonModule],<%}%><% if(inlineTemplate) { %>
template: `<p><%= fileName %> works!</p>`<% } else { %>
templateUrl: './<%= fileName %><%= type ? '.' + type : '' %>.html'<% } if(inlineStyle) { %>,
template: `<p><%= name %> works!</p>`<% } else { %>
templateUrl: './<%= fileName %>.html'<% } if(inlineStyle) { %>,
styles: [<% if(displayBlock){ %>
`
:host {
display: block;
}
`<% } %>
]<% } else if (style !== 'none') { %>,
styleUrls: ['./<%= fileName %><%= type ? '.' + type : '' %>.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>,
styleUrls: ['./<%= fileName %>.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>,
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= className %><%= typeClassName %> {}
export class <%= symbolName %> {}
4 changes: 2 additions & 2 deletions packages/angular/src/generators/component/lib/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export function findModuleFromOptions(
if (!options.module) {
return normalizePath(findModule(tree, options.directory, projectRoot));
} else {
const modulePath = joinPathFragments(options.path, options.module);
const modulePath = joinPathFragments(options.directory, options.module);
const componentPath = options.directory;
const moduleBaseName = basename(modulePath);

const candidateSet = new Set<string>([options.path]);
const candidateSet = new Set<string>([options.directory]);

const projectRootParent = dirname(projectRoot);
for (let dir = modulePath; dir !== projectRootParent; dir = dirname(dir)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import type { Tree } from '@nx/devkit';
import { readProjectConfiguration } from '@nx/devkit';
import { names, readProjectConfiguration } from '@nx/devkit';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import type { AngularProjectConfiguration } from '../../../utils/types';
import { normalizeNameAndPaths } from '../../utils/path';
import { buildSelector } from '../../utils/selector';
import type { NormalizedSchema, Schema } from '../schema';

export function normalizeOptions(
export async function normalizeOptions(
tree: Tree,
options: Schema
): NormalizedSchema {
): Promise<NormalizedSchema> {
options.type ??= 'component';
const { directory, filePath, name, path, root, sourceRoot } =
normalizeNameAndPaths(tree, options);
const {
artifactName: name,
directory,
fileName,
filePath,
project,
} = await determineArtifactNameAndDirectoryOptions(tree, {
artifactType: 'component',
callingGenerator: '@nx/angular:component',
name: options.name,
directory: options.directory ?? options.path,
flat: options.flat,
nameAndDirectoryFormat: options.nameAndDirectoryFormat,
project: options.project,
suffix: options.type ?? 'component',
});

const { className } = names(name);
const { className: suffixClassName } = names(options.type);
const symbolName = `${className}${suffixClassName}`;

const { prefix } = readProjectConfiguration(
const { prefix, root, sourceRoot } = readProjectConfiguration(
tree,
options.project
project
) as AngularProjectConfiguration;

const selector =
Expand All @@ -25,12 +43,13 @@ export function normalizeOptions(
return {
...options,
name,
project,
changeDetection: options.changeDetection ?? 'Default',
style: options.style ?? 'css',
flat: options.flat ?? false,
directory,
fileName,
filePath,
path,
symbolName,
projectSourceRoot: sourceRoot,
projectRoot: root,
selector,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import type { Tree } from '@nx/devkit';
import {
validatePathIsUnderProjectRoot,
validateProject,
validateStandaloneOption,
} from '../../utils/validations';
import { validateStandaloneOption } from '../../utils/validations';
import type { Schema } from '../schema';

export function validateOptions(tree: Tree, options: Schema): void {
validateProject(tree, options.project);
validatePathIsUnderProjectRoot(tree, options.project, options.path);
validateStandaloneOption(tree, options.standalone);
}
24 changes: 21 additions & 3 deletions packages/angular/src/generators/component/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { NameAndDirectoryFormat } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';

export interface Schema {
name: string;
project: string;
path?: string;
directory?: string;
nameAndDirectoryFormat?: NameAndDirectoryFormat;
displayBlock?: boolean;
inlineStyle?: boolean;
inlineTemplate?: boolean;
Expand All @@ -11,20 +13,36 @@ export interface Schema {
style?: 'css' | 'scss' | 'sass' | 'less' | 'none';
skipTests?: boolean;
type?: string;
flat?: boolean;
skipImport?: boolean;
selector?: string;
module?: string;
skipSelector?: boolean;
export?: boolean;
prefix?: string;
skipFormat?: boolean;

/**
* @deprecated Provide the `directory` option instead and use the `as-provided` format. It will be removed in Nx v18.
*/
flat?: boolean;
/**
* @deprecated Provide the `directory` option instead. It will be removed in Nx v18.
*/
path?: string;
/**
* @deprecated Provide the `directory` option instead. The project will be determined from the directory provided. It will be removed in Nx v18.
*/
project?: string;
}

export interface NormalizedSchema extends Schema {
directory: string;
filePath: string;
project: string;
projectSourceRoot: string;
projectRoot: string;
selector: string;

fileName: string;
symbolName: string;
}
Loading