Skip to content

Commit e417c89

Browse files
committed
feat(@schematics/angular): Add addTypeToClassName option to relevant schematics
Introduces a new `addTypeToClassName` option to the `component`, `directive`, and `service` schematics. This option controls whether the schematic's `type` (e.g., 'Component', 'Directive', 'Service') is appended to the generated class name. When `fileNameStyleGuide` is set to `'2016'` in `ng new`, `addTypeToClassName` is automatically set to `false` for these schematics in `angular.json`. This ensures that while file names follow the 2016 style (e.g., `app.component.ts`), class names remain concise (e.g., `AppComponent` instead of `AppComponentComponent`), aligning with future selectorless template conventions. This change provides greater flexibility in naming conventions, allowing users to choose between more verbose class names (default for 2025 style guide) and more concise ones (for 2016 style guide).
1 parent 4e6c94f commit e417c89

18 files changed

+141
-24
lines changed

packages/schematics/angular/application/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul
242242
schematicsWithTypeSymbols.forEach((type) => {
243243
const schematicDefaults = (schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject;
244244
schematicDefaults.type = type;
245+
schematicDefaults.addTypeToClassName = false;
245246
});
246247

247248
const schematicsWithTypeSeparator = ['guard', 'interceptor', 'module', 'pipe', 'resolver'];
@@ -408,6 +409,7 @@ function getComponentOptions(options: ApplicationOptions): Partial<ComponentOpti
408409

409410
if (options.fileNameStyleGuide === '2016') {
410411
componentOptions.type = 'component';
412+
componentOptions.addTypeToClassName = false;
411413
}
412414

413415
return componentOptions;

packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22

3-
import <% if(!exportDefault) { %>{ <% }%><%= classify(name) %><%= classify(type) %> <% if(!exportDefault) {%>} <% }%>from './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>';
3+
import <% if(!exportDefault) { %>{ <% }%><%= classifiedName %> <% if(!exportDefault) {%>} <% }%>from './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>';
44

5-
describe('<%= classify(name) %><%= classify(type) %>', () => {
6-
let component: <%= classify(name) %><%= classify(type) %>;
7-
let fixture: ComponentFixture<<%= classify(name) %><%= classify(type) %>>;
5+
describe('<%= classifiedName %>', () => {
6+
let component: <%= classifiedName %>;
7+
let fixture: ComponentFixture<<%= classifiedName %>>;
88

99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
11-
<%= standalone ? 'imports' : 'declarations' %>: [<%= classify(name) %><%= classify(type) %>]
11+
<%= standalone ? 'imports' : 'declarations' %>: [<%= classifiedName %>]
1212
})
1313
.compileComponents();
1414

15-
fixture = TestBed.createComponent(<%= classify(name) %><%= classify(type) %>);
15+
fixture = TestBed.createComponent(<%= classifiedName %>);
1616
component = fixture.componentInstance;
1717
fixture.detectChanges();
1818
});

packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ import { <% if(changeDetection !== 'Default') { %>ChangeDetectionStrategy, <% }%
1919
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
2020
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
2121
})
22-
export <% if(exportDefault) {%>default <%}%>class <%= classify(name) %><%= classify(type) %> {
22+
export <% if(exportDefault) {%>default <%}%>class <%= classifiedName %> {
2323

2424
}

packages/schematics/angular/component/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ export default createProjectSchematic<ComponentOptions>((options, { project, tre
5454
options.selector = options.selector || buildSelector(options, (project && project.prefix) || '');
5555

5656
validateHtmlSelector(options.selector);
57-
validateClassName(strings.classify(options.name));
57+
58+
const classifiedName =
59+
strings.classify(options.name) +
60+
(options.addTypeToClassName && options.type ? strings.classify(options.type) : '');
61+
validateClassName(classifiedName);
5862

5963
const skipStyleFile = options.inlineStyle || options.style === Style.None;
6064
const templateSource = apply(url('./files'), [
@@ -66,6 +70,8 @@ export default createProjectSchematic<ComponentOptions>((options, { project, tre
6670
'if-flat': (s: string) => (options.flat ? '' : s),
6771
'ngext': options.ngHtml ? '.ng' : '',
6872
...options,
73+
// Add a new variable for the classified name, conditionally including the type
74+
classifiedName,
6975
}),
7076
!options.type
7177
? forEach(((file) => {

packages/schematics/angular/component/index_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe('Component Schematic', () => {
163163

164164
await expectAsync(
165165
schematicRunner.runSchematic('component', options, appTree),
166-
).toBeRejectedWithError('Class name "404" is invalid.');
166+
).toBeRejectedWithError('Class name "404Component" is invalid.');
167167
});
168168

169169
it('should allow dash in selector before a number', async () => {

packages/schematics/angular/component/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@
9595
"type": "string",
9696
"description": "Append a custom type to the component's filename. For example, if you set the type to `container`, the file will be named `my-component.container.ts`."
9797
},
98+
"addTypeToClassName": {
99+
"type": "boolean",
100+
"default": true,
101+
"description": "When true, the 'type' option will be appended to the generated class name. When false, only the file name will include the type."
102+
},
98103
"skipTests": {
99104
"type": "boolean",
100105
"description": "Skip the generation of unit test files `spec.ts`.",
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { <%= classify(name) %><%= classify(type) %> } from './<%= dasherize(name) %><%= type ? '.' + dasherize(type) : '' %>';
1+
import { <%= classifiedName %> } from './<%= dasherize(name) %><%= type ? '.' + dasherize(type) : '' %>';
22

3-
describe('<%= classify(name) %><%= classify(type) %>', () => {
3+
describe('<%= classifiedName %>', () => {
44
it('should create an instance', () => {
5-
const directive = new <%= classify(name) %><%= classify(type) %>();
5+
const directive = new <%= classifiedName %>();
66
expect(directive).toBeTruthy();
77
});
88
});

packages/schematics/angular/directive/files/__name@dasherize__.__type@dasherize__.ts.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Directive } from '@angular/core';
44
selector: '[<%= selector %>]'<% if(!standalone) {%>,
55
standalone: false<%}%>
66
})
7-
export class <%= classify(name) %><%= classify(type) %> {
7+
export class <%= classifiedName %> {
88

99
constructor() { }
1010

packages/schematics/angular/directive/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ export default createProjectSchematic<DirectiveOptions>((options, { project, tre
3939
options.selector = options.selector || buildSelector(options, project.prefix || '');
4040

4141
validateHtmlSelector(options.selector);
42-
validateClassName(strings.classify(options.name));
42+
const classifiedName =
43+
strings.classify(options.name) +
44+
(options.addTypeToClassName && options.type ? strings.classify(options.type) : '');
45+
validateClassName(classifiedName);
4346

4447
return chain([
4548
addDeclarationToNgModule({
4649
type: 'directive',
4750

4851
...options,
4952
}),
50-
generateFromFiles(options),
53+
generateFromFiles({
54+
...options,
55+
classifiedName,
56+
}),
5157
]);
5258
});

packages/schematics/angular/directive/index_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,35 @@ describe('Directive Schematic', () => {
137137
expect(testContent).toContain("describe('Foo'");
138138
});
139139

140+
it('should not add type to class name when addTypeToClassName is false', async () => {
141+
const options = { ...defaultOptions, type: 'Directive', addTypeToClassName: false };
142+
const tree = await schematicRunner.runSchematic('directive', options, appTree);
143+
const content = tree.readContent('/projects/bar/src/app/foo.directive.ts');
144+
const testContent = tree.readContent('/projects/bar/src/app/foo.directive.spec.ts');
145+
expect(content).toContain('export class Foo {');
146+
expect(content).not.toContain('export class FooDirective {');
147+
expect(testContent).toContain("describe('Foo', () => {");
148+
expect(testContent).not.toContain("describe('FooDirective', () => {");
149+
});
150+
151+
it('should add type to class name when addTypeToClassName is true', async () => {
152+
const options = { ...defaultOptions, type: 'Directive', addTypeToClassName: true };
153+
const tree = await schematicRunner.runSchematic('directive', options, appTree);
154+
const content = tree.readContent('/projects/bar/src/app/foo.directive.ts');
155+
const testContent = tree.readContent('/projects/bar/src/app/foo.directive.spec.ts');
156+
expect(content).toContain('export class FooDirective {');
157+
expect(testContent).toContain("describe('FooDirective', () => {");
158+
});
159+
160+
it('should add type to class name by default', async () => {
161+
const options = { ...defaultOptions, type: 'Directive', addTypeToClassName: undefined };
162+
const tree = await schematicRunner.runSchematic('directive', options, appTree);
163+
const content = tree.readContent('/projects/bar/src/app/foo.directive.ts');
164+
const testContent = tree.readContent('/projects/bar/src/app/foo.directive.spec.ts');
165+
expect(content).toContain('export class FooDirective {');
166+
expect(testContent).toContain("describe('FooDirective', () => {");
167+
});
168+
140169
describe('standalone=false', () => {
141170
const defaultNonStandaloneOptions: DirectiveOptions = {
142171
...defaultOptions,

0 commit comments

Comments
 (0)