angular/packages/compiler/src/service_compiler.ts
Kristiyan Kostadinov 8f3d0b9d97 feat(core): introduce @Service decorator
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);
  }
}
```
2026-04-22 11:01:01 -07:00

58 lines
1.6 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 {createInjectableType, delegateToFactory} from './injectable_compiler_2';
import * as o from './output/output_ast';
import {Identifiers} from './render3/r3_identifiers';
import {R3CompiledExpression, R3Reference} from './render3/util';
import {DefinitionMap} from './render3/view/util';
export interface R3ServiceMetadata {
name: string;
type: R3Reference;
typeArgumentCount: number;
autoProvided?: boolean;
factory?: o.Expression;
}
export function compileService(
meta: R3ServiceMetadata,
resolveForwardRefs: boolean,
): R3CompiledExpression {
const def = new DefinitionMap<{
token: o.Expression;
factory: o.Expression;
autoProvided: o.Expression;
}>();
def.set('token', meta.type.value);
def.set(
'factory',
meta.factory === undefined
? delegateToFactory(
meta.type.value as o.WrappedNodeExpr<any>,
meta.type.value as o.WrappedNodeExpr<any>,
resolveForwardRefs,
)
: o.arrowFn([], meta.factory.callFn([])),
);
// Only generate providedIn property if it's different from the default.
if (meta.autoProvided === false) {
def.set('autoProvided', o.literal(false));
}
const expression = o
.importExpr(Identifiers.defineService)
.callFn([def.toLiteralMap()], undefined, true);
return {
expression,
type: createInjectableType(meta.type.type, meta.typeArgumentCount),
statements: [],
};
}