1
0
mirror of https://github.com/angular/angular-cli.git synced 2025-05-15 18:13:38 +08:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Angular Robot
637316c492
Merge da94f638d9e0167d73023d615438f41c3c1b305c into fe69a9b23aa4364774a8c38027fdb9149d8e1659 2025-03-18 15:03:49 +00:00
Angular Robot
da94f638d9 build: update devinfra digest to 36bfe0a 2025-03-18 15:03:44 +00:00
Paul Gschwendtner
fe69a9b23a build: improve debugging mode of e2e test runner
Currently the `while` loop may either be causing the process to be
really stuck/hanging, or it somehow causes Node to exit.

This change makes the logic more robust and less CPU consuming.
2025-03-18 14:52:40 +01:00
Charles Lyding
bc0f07b484 fix(@schematics/angular): generate services without a .service extension/type
To align with the updated style guide, Angular v20 will generate services
without a `.service` file extension type for all service related
files by default. Projects will automatically use this naming convention.
Projects can however opt-out by setting the `type` option to `Service`
for the service schematic. This can be done as a default in the `angular.json`
or directly on the commandline via `--type=Service` when executing `ng generate`.
As an example, `example.service.ts` will now be named `example.ts`. Additionally,
the TypeScript class name will be `Example` instead of the previous `ExampleService`.
2025-03-18 07:25:46 -04:00
Charles Lyding
e7ae25a76b refactor(@schematics/angular): remove unneeded initial library service generation
The `library` schematic will now longer generate an empty Angular service
with the name of the library. Using only the name would generate a file
name conflict with the component now that the type suffix is no longer
used by default. Additionally, services tend to be specific to a particular
behavior and/or functionality and are named as such. A generically named
empty service will typically be deleted or renamed.
2025-03-18 07:25:46 -04:00
15 changed files with 72 additions and 53 deletions

@ -234,7 +234,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "devinfra",
commit = "85eab901e27abe60bb725fbfd8def94559cbe636",
commit = "36bfe0a0b79681f75c17049b50123153fc3b02c8",
remote = "https://github.com/angular/dev-infra.git",
)

@ -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';<% } %>

@ -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());

@ -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',
]),
);
});

@ -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();
});
});

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
export class <%= classify(name) %><%= classify(type) %> {
constructor() { }
}

@ -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();
});
});

@ -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),
});

@ -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'");
});
});

@ -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"]

@ -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))),
]);

@ -17,7 +17,7 @@ export async function libraryConsumptionSetup(): Promise<void> {
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<void> {
export class App {
title = 'test-project';
constructor(myLibService: MyLibService) {
console.log(myLibService);
constructor() {
}
}
`,

@ -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'))

@ -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',

@ -257,10 +257,8 @@ Promise.all([findFreePort(), findFreePort(), findPackageTars()])
console.log(`Current Directory: ${process.cwd()}`);
console.log('Will loop forever while you debug... CTRL-C to quit.');
/* eslint-disable no-constant-condition */
while (1) {
// That's right!
}
// Wait forever until user explicitly cancels.
await new Promise(() => {});
}
process.exitCode = 1;