Skip to content

Commit 4e6c94f

Browse files
committed
feat(@schematics/angular): support different file name style guides in ng new
Introduces the ability to configure the file naming convention for generated files directly within the `ng new` schematic. This allows users to create new workspaces that adhere to the 2016 style guide conventions, as an alternative to the default 2025 style guide. For more information, see the Angular Style Guide (https://angular.dev/style-guide). When a user runs `ng new --file-name-style-guide 2016`: - The `ng-new` schematic passes the style guide option down to the `application` sub-schematic. - The `application` schematic configures the `schematics` section of the new `angular.json` to use the 2016 naming conventions for future `ng generate` commands. - The `application` schematic generates the initial application files with the appropriate suffixes (e.g., `app.component.ts`). This addresses community feedback requesting a way to maintain the previous file naming structure for consistency in existing projects and workflows. Fixes #30594
1 parent 86abd8e commit 4e6c94f

File tree

10 files changed

+64
-1
lines changed

10 files changed

+64
-1
lines changed

packages/schematics/angular/application/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export default function (options: ApplicationOptions): Rule {
6767
const { appDir, appRootSelector, componentOptions, folderName, sourceDir } =
6868
await getAppOptions(host, options);
6969

70+
const suffix = options.fileNameStyleGuide === '2016' ? '.component' : '';
71+
7072
return chain([
7173
addAppToWorkspaceFile(options, appDir),
7274
addTsProjectReference('./' + join(normalize(appDir), 'tsconfig.app.json')),
@@ -108,6 +110,7 @@ export default function (options: ApplicationOptions): Rule {
108110
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(appDir),
109111
appName: options.name,
110112
folderName,
113+
suffix,
111114
}),
112115
move(appDir),
113116
]),
@@ -119,7 +122,7 @@ export default function (options: ApplicationOptions): Rule {
119122
? filter((path) => !path.endsWith('tsconfig.spec.json.template'))
120123
: noop(),
121124
componentOptions.inlineTemplate
122-
? filter((path) => !path.endsWith('app.html.template'))
125+
? filter((path) => !path.endsWith('app__suffix__.html.template'))
123126
: noop(),
124127
applyTemplates({
125128
utils: strings,
@@ -128,6 +131,7 @@ export default function (options: ApplicationOptions): Rule {
128131
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(appDir),
129132
appName: options.name,
130133
folderName,
134+
suffix,
131135
}),
132136
move(appDir),
133137
]),
@@ -233,6 +237,19 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul
233237
});
234238
}
235239

240+
if (options.fileNameStyleGuide === '2016') {
241+
const schematicsWithTypeSymbols = ['component', 'directive', 'service'];
242+
schematicsWithTypeSymbols.forEach((type) => {
243+
const schematicDefaults = (schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject;
244+
schematicDefaults.type = type;
245+
});
246+
247+
const schematicsWithTypeSeparator = ['guard', 'interceptor', 'module', 'pipe', 'resolver'];
248+
schematicsWithTypeSeparator.forEach((type) => {
249+
((schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject).typeSeparator = '.';
250+
});
251+
}
252+
236253
const sourceRoot = join(normalize(projectRoot), 'src');
237254
let budgets: { type: string; maximumWarning: string; maximumError: string }[] = [];
238255
if (options.strict) {
@@ -389,5 +406,9 @@ function getComponentOptions(options: ApplicationOptions): Partial<ComponentOpti
389406
viewEncapsulation: options.viewEncapsulation,
390407
};
391408

409+
if (options.fileNameStyleGuide === '2016') {
410+
componentOptions.type = 'component';
411+
}
412+
392413
return componentOptions;
393414
}

packages/schematics/angular/application/schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@
127127
"x-prompt": "Do you want to create a 'zoneless' application without zone.js?",
128128
"type": "boolean",
129129
"default": false
130+
},
131+
"fileNameStyleGuide": {
132+
"type": "string",
133+
"enum": ["2016", "2025"],
134+
"default": "2025",
135+
"description": "The file naming convention to use for generated files. The '2025' style guide (default) uses a concise format (e.g., `app.ts` for the root component), while the '2016' style guide includes the type in the file name (e.g., `app.component.ts`). For more information, see the Angular Style Guide (https://angular.dev/style-guide)."
130136
}
131137
},
132138
"required": ["name"]

packages/schematics/angular/ng-new/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export default function (options: NgNewOptions): Rule {
5858
standalone: options.standalone,
5959
ssr: options.ssr,
6060
zoneless: options.zoneless,
61+
fileNameStyleGuide: options.fileNameStyleGuide,
6162
};
6263

6364
return chain([

packages/schematics/angular/ng-new/index_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,33 @@ describe('Ng New Schematic', () => {
128128
const stylesContent = tree.readContent('/bar/src/styles.css');
129129
expect(stylesContent).toContain('@import "tailwindcss";');
130130
});
131+
132+
it(`should create files with file name style guide '2016'`, async () => {
133+
const options = { ...defaultOptions, fileNameStyleGuide: '2016' };
134+
135+
const tree = await schematicRunner.runSchematic('ng-new', options);
136+
const files = tree.files;
137+
expect(files).toEqual(
138+
jasmine.arrayContaining([
139+
'/bar/src/app/app.component.css',
140+
'/bar/src/app/app.component.html',
141+
'/bar/src/app/app.component.spec.ts',
142+
'/bar/src/app/app.component.ts',
143+
]),
144+
);
145+
146+
const {
147+
projects: {
148+
'foo': { schematics },
149+
},
150+
} = JSON.parse(tree.readContent('/bar/angular.json'));
151+
expect(schematics['@schematics/angular:component'].type).toBe('component');
152+
expect(schematics['@schematics/angular:directive'].type).toBe('directive');
153+
expect(schematics['@schematics/angular:service'].type).toBe('service');
154+
expect(schematics['@schematics/angular:guard'].typeSeparator).toBe('.');
155+
expect(schematics['@schematics/angular:interceptor'].typeSeparator).toBe('.');
156+
expect(schematics['@schematics/angular:module'].typeSeparator).toBe('.');
157+
expect(schematics['@schematics/angular:pipe'].typeSeparator).toBe('.');
158+
expect(schematics['@schematics/angular:resolver'].typeSeparator).toBe('.');
159+
});
131160
});

packages/schematics/angular/ng-new/schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@
151151
"type": "string",
152152
"enum": ["none", "gemini", "copilot", "claude", "cursor", "jetbrains", "windsurf"]
153153
}
154+
},
155+
"fileNameStyleGuide": {
156+
"type": "string",
157+
"enum": ["2016", "2025"],
158+
"default": "2025",
159+
"description": "The file naming convention to use for generated files. The '2025' style guide (default) uses a concise format (e.g., `app.ts` for the root component), while the '2016' style guide includes the type in the file name (e.g., `app.component.ts`). For more information, see the Angular Style Guide (https://angular.dev/style-guide)."
154160
}
155161
},
156162
"required": ["name", "version"]

0 commit comments

Comments
 (0)