mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
8f3d0b9d97
These changes introduce the new `@Service` decorator which is a more ergonomic alternative to `@Injectable`. The reason we're adding a new decorator is that `@Injectable` has been around since the beginning of Angular and it has a lot of baggage that adds unnecessary overhead for users that generally want to define a singleton service, available in their entire app. The key differences between `@Service` and `@Injectable` are:
1. `@Service` is `providedIn: 'root'` by default. You can opt into providing the service yourself by setting `autoProvided: false` on it.
2. `@Service` doesn't allow constructor-based injection, only the `inject` function.
3. `@Service` doesn't support the complex type signature of `@Injectable` (`useClass`, `useValue` etc.). Instead it supports a single `factory` function.
Example:
```ts
import {Service} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {AuthService} from './auth';
@Service()
export class PostService {
private readonly httpClient = inject(HttpClient);
private readonly authService = inject(AuthService);
getUserPosts() {
return this.httpClient.get('/api/posts/' + this.authService.userId);
}
}
```
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
/*!
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
|
import {NgtscTestEnvironment} from './env';
|
|
|
|
const testFiles = loadStandardTestFiles();
|
|
|
|
runInEachFileSystem(() => {
|
|
describe('@Service decorator', () => {
|
|
let env!: NgtscTestEnvironment;
|
|
|
|
beforeEach(() => {
|
|
env = NgtscTestEnvironment.setup(testFiles);
|
|
env.tsconfig();
|
|
});
|
|
|
|
// More thorough compilation tests are in `packages/compiler-cli/test/compliance/test_cases/service_decorator`.
|
|
it('should compile an @Service class', () => {
|
|
env.write(
|
|
'test.ts',
|
|
`
|
|
import {Service} from '@angular/core';
|
|
|
|
@Service()
|
|
export class TestService {}
|
|
`,
|
|
);
|
|
env.driveMain();
|
|
|
|
const jsContents = env.getContents('test.js');
|
|
const dtsContents = env.getContents('test.d.ts');
|
|
expect(jsContents).not.toContain('__decorate');
|
|
expect(jsContents).toContain('TestService.ɵfac =');
|
|
expect(jsContents).toContain('TestService.ɵprov =');
|
|
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<TestService, never>;');
|
|
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration<TestService>;');
|
|
});
|
|
|
|
it('should report if an @Service class has another Angular decorator', () => {
|
|
env.write(
|
|
'test.ts',
|
|
`
|
|
import {Service, Pipe} from '@angular/core';
|
|
|
|
@Service()
|
|
@Pipe({name: 'foo'})
|
|
export class TestService {}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(1);
|
|
expect(diags[0].messageText).toBe(
|
|
'Cannot apply more than one Angular decorator on an @Service class.',
|
|
);
|
|
});
|
|
|
|
it('should report if an @Service class uses constructor-based DI from its own constructor', () => {
|
|
env.write(
|
|
'test.ts',
|
|
`
|
|
import {Service, ApplicationRef} from '@angular/core';
|
|
|
|
@Service()
|
|
export class TestService {
|
|
constructor(appRef: ApplicationRef) {}
|
|
}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(1);
|
|
expect(diags[0].messageText).toBe(
|
|
'@Service class cannot use constructor dependency injection. Use the `inject` function instead.',
|
|
);
|
|
});
|
|
|
|
it('should report if an @Service class uses constructor-based DI from an inherited constructor', () => {
|
|
env.write(
|
|
'grandparent.ts',
|
|
`
|
|
import {Injectable, ApplicationRef} from '@angular/core';
|
|
|
|
@Injectable()
|
|
export class GrandparentService {
|
|
constructor(appRef: ApplicationRef) {}
|
|
}
|
|
`,
|
|
);
|
|
|
|
env.write(
|
|
'parent.ts',
|
|
`
|
|
import {Injectable} from '@angular/core';
|
|
import {GrandparentService} from './grandparent';
|
|
|
|
@Injectable()
|
|
export class ParentService extends GrandparentService {}
|
|
`,
|
|
);
|
|
|
|
env.write(
|
|
'test.ts',
|
|
`
|
|
import {Service} from '@angular/core';
|
|
import {ParentService} from './parent';
|
|
|
|
@Service()
|
|
export class TestService extends ParentService {}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(1);
|
|
expect(diags[0].messageText).toBe(
|
|
'@Service class cannot use constructor dependency injection. Use the `inject` function instead.',
|
|
);
|
|
});
|
|
});
|
|
});
|