diff --git a/packages/schematics/angular/library/files/src/__entryFile__.ts.template b/packages/schematics/angular/library/files/src/__entryFile__.ts.template index 9a737ecf69f8..1db55ca95e33 100644 --- a/packages/schematics/angular/library/files/src/__entryFile__.ts.template +++ b/packages/schematics/angular/library/files/src/__entryFile__.ts.template @@ -2,6 +2,5 @@ * Public API Surface of <%= dasherize(name) %> */ -export * from './lib/<%= dasherize(name) %>.service'; export * from './lib/<%= dasherize(name) %>';<% if (!standalone) { %> export * from './lib/<%= dasherize(name) %>.module';<% } %> diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index 6bee27bc9ec1..f59c8420619b 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -185,12 +185,6 @@ export default function (options: LibraryOptions): Rule { standalone: options.standalone, project: packageName, }), - schematic('service', { - name: options.name, - flat: true, - path: sourceDir, - project: packageName, - }), (_tree: Tree, context: SchematicContext) => { if (!options.skipPackageJson && !options.skipInstall) { context.addTask(new NodePackageInstallTask()); diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index aebc908d3d90..7787571472e5 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -57,8 +57,6 @@ describe('Library Schematic', () => { '/projects/foo/src/my-index.ts', '/projects/foo/src/lib/foo.spec.ts', '/projects/foo/src/lib/foo.ts', - '/projects/foo/src/lib/foo.service.spec.ts', - '/projects/foo/src/lib/foo.service.ts', ]), ); }); @@ -102,8 +100,6 @@ describe('Library Schematic', () => { '/some/other/directory/bar/src/my-index.ts', '/some/other/directory/bar/src/lib/foo.spec.ts', '/some/other/directory/bar/src/lib/foo.ts', - '/some/other/directory/bar/src/lib/foo.service.spec.ts', - '/some/other/directory/bar/src/lib/foo.service.ts', ]), ); }); @@ -207,10 +203,8 @@ describe('Library Schematic', () => { const project = config.projects.pascalCasedName; expect(project).toBeDefined(); expect(project.root).toEqual('projects/pascal-cased-name'); - const svcContent = tree.readContent( - '/projects/pascal-cased-name/src/lib/pascal-cased-name.service.ts', - ); - expect(svcContent).toMatch(/providedIn: 'root'/); + const svcContent = tree.readContent('/projects/pascal-cased-name/src/lib/pascal-cased-name.ts'); + expect(svcContent).toContain('@Component'); }); describe(`update package.json`, () => { @@ -320,7 +314,6 @@ describe('Library Schematic', () => { const pkgJsonPath = '/projects/myscope/mylib/package.json'; expect(tree.files).toContain(pkgJsonPath); - expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.service.ts'); expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.ts'); const pkgJson = JSON.parse(tree.readContent(pkgJsonPath)); @@ -431,8 +424,6 @@ describe('Library Schematic', () => { '/projects/foo/src/lib/foo.module.ts', '/projects/foo/src/lib/foo.spec.ts', '/projects/foo/src/lib/foo.ts', - '/projects/foo/src/lib/foo.service.spec.ts', - '/projects/foo/src/lib/foo.service.ts', ]), ); }); diff --git a/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template b/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template new file mode 100644 index 000000000000..a57a4e043b4b --- /dev/null +++ b/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { <%= classify(name) %><%= classify(type) %> } from './<%= dasherize(name) %><%= type ? '.' + dasherize(type) : '' %>'; + +describe('<%= classify(name) %><%= classify(type) %>', () => { + let service: <%= classify(name) %><%= classify(type) %>; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(<%= classify(name) %><%= classify(type) %>); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.ts.template b/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template similarity index 64% rename from packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.ts.template rename to packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template index f14985e32b60..ad3685368077 100644 --- a/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.ts.template +++ b/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) -export class <%= classify(name) %>Service { +export class <%= classify(name) %><%= classify(type) %> { constructor() { } } diff --git a/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.spec.ts.template b/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.spec.ts.template deleted file mode 100644 index 2c7ab1d2bdf6..000000000000 --- a/packages/schematics/angular/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.spec.ts.template +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service'; - -describe('<%= classify(name) %>Service', () => { - let service: <%= classify(name) %>Service; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(<%= classify(name) %>Service); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/packages/schematics/angular/service/index.ts b/packages/schematics/angular/service/index.ts index 79073504978a..f832af457822 100644 --- a/packages/schematics/angular/service/index.ts +++ b/packages/schematics/angular/service/index.ts @@ -15,6 +15,9 @@ export default function (options: ServiceOptions): Rule { const flat = options.flat; options.flat = true; + // Schematic templates require a defined type value + options.type ??= ''; + return generateFromFiles(options, { 'if-flat': (s: string) => (flat ? '' : s), }); diff --git a/packages/schematics/angular/service/index_spec.ts b/packages/schematics/angular/service/index_spec.ts index 30e9745348dc..b5a6856e1504 100644 --- a/packages/schematics/angular/service/index_spec.ts +++ b/packages/schematics/angular/service/index_spec.ts @@ -46,15 +46,15 @@ describe('Service Schematic', () => { const tree = await schematicRunner.runSchematic('service', options, appTree); const files = tree.files; - expect(files).toContain('/projects/bar/src/app/foo/foo.service.spec.ts'); - expect(files).toContain('/projects/bar/src/app/foo/foo.service.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.ts'); }); it('service should be tree-shakeable', async () => { const options = { ...defaultOptions }; const tree = await schematicRunner.runSchematic('service', options, appTree); - const content = tree.readContent('/projects/bar/src/app/foo/foo.service.ts'); + const content = tree.readContent('/projects/bar/src/app/foo/foo.ts'); expect(content).toMatch(/providedIn: 'root'/); }); @@ -63,8 +63,8 @@ describe('Service Schematic', () => { const tree = await schematicRunner.runSchematic('service', options, appTree); const files = tree.files; - expect(files).toContain('/projects/bar/src/app/foo/foo.service.ts'); - expect(files).not.toContain('/projects/bar/src/app/foo/foo.service.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.ts'); + expect(files).not.toContain('/projects/bar/src/app/foo/foo.spec.ts'); }); it('should respect the sourceRoot value', async () => { @@ -72,6 +72,24 @@ describe('Service Schematic', () => { config.projects.bar.sourceRoot = 'projects/bar/custom'; appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); appTree = await schematicRunner.runSchematic('service', defaultOptions, appTree); - expect(appTree.files).toContain('/projects/bar/custom/app/foo/foo.service.ts'); + expect(appTree.files).toContain('/projects/bar/custom/app/foo/foo.ts'); + }); + + it('should respect the type option', async () => { + const options = { ...defaultOptions, type: 'Service' }; + const tree = await schematicRunner.runSchematic('service', options, appTree); + const content = tree.readContent('/projects/bar/src/app/foo/foo.service.ts'); + const testContent = tree.readContent('/projects/bar/src/app/foo/foo.service.spec.ts'); + expect(content).toContain('export class FooService'); + expect(testContent).toContain("describe('FooService'"); + }); + + it('should allow empty string in the type option', async () => { + const options = { ...defaultOptions, type: '' }; + const tree = await schematicRunner.runSchematic('service', options, appTree); + const content = tree.readContent('/projects/bar/src/app/foo/foo.ts'); + const testContent = tree.readContent('/projects/bar/src/app/foo/foo.spec.ts'); + expect(content).toContain('export class Foo'); + expect(testContent).toContain("describe('Foo'"); }); }); diff --git a/packages/schematics/angular/service/schema.json b/packages/schematics/angular/service/schema.json index d5a4d0418acd..29f5474e68dd 100644 --- a/packages/schematics/angular/service/schema.json +++ b/packages/schematics/angular/service/schema.json @@ -39,6 +39,10 @@ "type": "boolean", "description": "Skip the generation of a unit test file `spec.ts` for the service.", "default": false + }, + "type": { + "type": "string", + "description": "Append a custom type to the service's filename. For example, if you set the type to `service`, the file will be named `my-service.service.ts`." } }, "required": ["name", "project"] diff --git a/packages/schematics/angular/utility/generate-from-files.ts b/packages/schematics/angular/utility/generate-from-files.ts index 7cb1317e3771..98dd04ec4158 100644 --- a/packages/schematics/angular/utility/generate-from-files.ts +++ b/packages/schematics/angular/utility/generate-from-files.ts @@ -7,12 +7,14 @@ */ import { + FileOperator, Rule, Tree, apply, applyTemplates, chain, filter, + forEach, mergeWith, move, noop, @@ -31,6 +33,7 @@ export interface GenerateFromFilesOptions { project: string; skipTests?: boolean; templateFilesDirectory?: string; + type?: string; } export function generateFromFiles( @@ -56,6 +59,16 @@ export function generateFromFiles( ...options, ...extraTemplateValues, }), + !options.type + ? forEach(((file) => { + return file.path.includes('..') + ? { + content: file.content, + path: file.path.replace('..', '.'), + } + : file; + }) as FileOperator) + : noop(), move(parsedPath.path + (options.flat ? '' : '/' + strings.dasherize(options.name))), ]); diff --git a/tests/legacy-cli/e2e/tests/build/library/setup.ts b/tests/legacy-cli/e2e/tests/build/library/setup.ts index d47fcae5a754..13b658f345ba 100644 --- a/tests/legacy-cli/e2e/tests/build/library/setup.ts +++ b/tests/legacy-cli/e2e/tests/build/library/setup.ts @@ -17,7 +17,7 @@ export async function libraryConsumptionSetup(): Promise { export class MyLibComponent {}`, './src/app/app.ts': ` import { Component } from '@angular/core'; - import { MyLibService, MyLibComponent } from 'my-lib'; + import { MyLibComponent } from 'my-lib'; @Component({ standalone: true, @@ -28,8 +28,7 @@ export async function libraryConsumptionSetup(): Promise { export class App { title = 'test-project'; - constructor(myLibService: MyLibService) { - console.log(myLibService); + constructor() { } } `, diff --git a/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts b/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts index 26a18fc8a9d2..a7ddb3cb8310 100644 --- a/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts @@ -9,8 +9,8 @@ export default function () { return ( ng('generate', 'service', 'test-service') .then(() => expectFileToExist(serviceDir)) - .then(() => expectFileToExist(join(serviceDir, 'test-service.service.ts'))) - .then(() => expectFileToExist(join(serviceDir, 'test-service.service.spec.ts'))) + .then(() => expectFileToExist(join(serviceDir, 'test-service.ts'))) + .then(() => expectFileToExist(join(serviceDir, 'test-service.spec.ts'))) // Try to run the unit tests. .then(() => ng('test', '--watch=false')) diff --git a/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts b/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts index 463a3b971119..c8fad0e07954 100644 --- a/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts +++ b/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts @@ -2,10 +2,10 @@ import { prependToFile, replaceInFile } from '../../utils/fs'; import { ng } from '../../utils/process'; export default async function () { - await ng('generate', 'service', 'user'); + await ng('generate', 'service', 'user-service'); // Update the application to use the new service - await prependToFile('src/app/app.ts', "import { UserService } from './user.service';"); + await prependToFile('src/app/app.ts', "import { UserService } from './user-service';"); await replaceInFile( 'src/app/app.ts',