Skip to content

feat(core): introduce @Service decorator#68195

Open
crisbeto wants to merge 1 commit intoangular:mainfrom
crisbeto:service
Open

feat(core): introduce @Service decorator#68195
crisbeto wants to merge 1 commit intoangular:mainfrom
crisbeto:service

Conversation

@crisbeto
Copy link
Copy Markdown
Member

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:

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

@crisbeto crisbeto added action: review The PR is still awaiting reviews from at least one requested reviewer target: minor This PR is targeted for the next minor release labels Apr 14, 2026
@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: core Issues related to the framework runtime labels Apr 14, 2026
@ngbot ngbot bot added this to the Backlog milestone Apr 14, 2026
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);
  }
}
```
@crisbeto crisbeto marked this pull request as ready for review April 14, 2026 08:52
@MunMunMiao
Copy link
Copy Markdown

you can refer to the functional dependency injection solution I have implemented 🤣

https://github.com/MunMunMiao/dn-ioc

@github-actions
Copy link
Copy Markdown

Deployed adev-preview for 4a65d03 to: https://ng-dev-previews-fw--pr-angular-angular-68195-adev-prev-gcwb19ii.web.app

Note: As new commits are pushed to this pull request, this link is updated after the preview is rebuilt.

@fmalcher
Copy link
Copy Markdown
Contributor

Haha, pretty much what @JohannesHoppe foresaw last year 😬 https://angular.schule/blog/2025-09-service-decorator

/**
* A function to invoke to create a value for this service.
*/
factory?: () => unknown;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Would we want to have some kind of typesafety on the factory ?
Like ensure that we returning something that matches the decorated class ? (not even sure if its doable).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the decorator type can be based on the class it's set on like this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could do that. here's an example for non-experimental decorators:

declare function Service<T>(options?: {
  factory?: () => NoInfer<T>
}): (target: abstract new (...args: any) => T) => void;

@Service({
  factory: () => ({}), // TS2322: Type () => {} is not assignable to type () => NoInfer<Foo>. Property prop is missing in type {} but required in type Foo
})
export class Foo {
  prop = 1;
}

@pullapprove pullapprove bot requested a review from JeanMeche April 14, 2026 12:01
}

const fixture = TestBed.createComponent(App);
fixture.detectChanges();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Is there a reason to use detectChanges instead of whenStable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make sure that the query was evaluated.

@ronnain
Copy link
Copy Markdown

ronnain commented Apr 14, 2026

I don’t think this would be a useful addition to Angular.

There are already ESLint rules encouraging the use of inject() instead of constructors.

Introducing a @service decorator would also make a large portion of existing articles, documentation, and videos—currently based on @Injectable({ providedIn: 'root' })—effectively outdated.

It could also create confusion for AI tools.

More importantly, I don’t see it bringing significant value.

One of the main issues with services, in my opinion, is injecting services that are not properly provided. This leads to runtime errors that could potentially be avoided.

I don’t think introducing a @service decorator would help address this problem.

On the other hand, a more functional approach could start laying the groundwork to gradually solve this issue.

@JohannesHoppe
Copy link
Copy Markdown
Contributor

@ronnain

With Angular 20, the service suffix has been removed from the style guide. While this leads to shorter filenames, it also makes the role of classes less obvious. It is no longer recommended to suffix components, services, and directives. That's generally a great idea. We get shorter filenames and more emphasis on deliberate naming. But there is one small downside: We no longer immediately recognize that a class is intended to be used as a service.

until Angular 19 - the word "service" is somewhere

// book-store.service.ts

@Injectable({
  providedIn: 'root'
})
export class BookStoreService { }

starting from Angular 20, WTF is this?

// book-store.ts

@Injectable({
  providedIn: 'root'
})
export class BookStore { }

Of course, anyone who has used Angular for a while knows that the @Injectable decorator almost always indicates a service. Nevertheless, in my opinion, the intended use of the class could/should be communicated even more clearly. For me, @Service makes so much sense! It's a service, so we decorate it accordingly!

@KhizerRehan
Copy link
Copy Markdown

@ronnain

With Angular 20, the service suffix has been removed from the style guide. While this leads to shorter filenames, it also makes the role of classes less obvious. It is no longer recommended to suffix components, services, and directives. That's generally a great idea. We get shorter filenames and more emphasis on deliberate naming. But there is one small downside: We no longer immediately recognize that a class is intended to be used as a service.

until Angular 19 - the word "service" is somewhere

// book-store.service.ts

@Injectable({
  providedIn: 'root'
})
export class BookStoreService { }

starting from Angular 20, WTF is this?

// book-store.ts

@Injectable({
  providedIn: 'root'
})
export class BookStore { }

Of course, anyone who has used Angular for a while knows that the @Injectable decorator almost always indicates a service. Nevertheless, in my opinion, the intended use of the class could/should be communicated even more clearly. For me, @Service makes so much sense! It's a service, so we decorate it accordingly!

IMO, @Service() feels like a nicer, more semantic wrapper over @Injectable({ providedIn: 'root' }). I am not sure how it will impact legacy codebases as it is said "doesn't support the complex type signature " but tbh, i like this update.

I also assume there would be some migrations in near future to update codebase in 1 go with new updates/latest feature migrations

}): ɵɵInjectableDeclaration<T> {
return {
token: opts.token,
providedIn: opts.autoProvided === false ? null : 'root',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @Service() decorator is a great ergonomic improvement for the common case. However, there's a consistency concern worth discussing.

Since @Service() doesn't support providedIn: 'platform', codebases that need platform-scoped services end up mixing decorators:

@Service() // "normal" services
export class UserStore {}  

@Injectable({ providedIn: 'platform' }) // wait, why is this different? I'm service too
export class UserStoreShared {}

Both are conceptually services - only their scope differs. Having two decorators for the same concept based on configuration feels like it violates the principle of least surprise, especially for developers new to a codebase who'd have to understand why two seemingly identical things are declared differently.

Since @Service() already accepts options (autoProvided, factory), supporting providedIn: 'platform' wouldn't compromise the simplicity goal, and it would eliminate the mixed-decorator problem:

@Service({ providedIn: 'platform' })
export class UserStoreShared {}

A useful parallel: Angular didn't split @Component into two decorators when standalone was introduced — it extended the existing one. The same logic could apply here.

Was this considered? Is the intent that @Injectable remains the decorator for "non-standard" DI configurations?

}): ɵɵInjectableDeclaration<T> {
return {
token: opts.token,
providedIn: opts.autoProvided === false ? null : 'root',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth asking the question. Why do we choose the 'root' injector and not the 'platform' which above 'root'

One argument against using platform, would be that support is port for several others features (#64090)

@pullapprove pullapprove bot requested a review from JeanMeche April 14, 2026 21:07
@erkamyaman
Copy link
Copy Markdown
Contributor

erkamyaman commented Apr 14, 2026

I don’t think this would be a useful addition to Angular.

There are already ESLint rules encouraging the use of inject() instead of constructors.

Introducing a @service decorator would also make a large portion of existing articles, documentation, and videos—currently based on @Injectable({ providedIn: 'root' })—effectively outdated.

It could also create confusion for AI tools.

More importantly, I don’t see it bringing significant value.

One of the main issues with services, in my opinion, is injecting services that are not properly provided. This leads to runtime errors that could potentially be avoided.

I don’t think introducing a @service decorator would help address this problem.

On the other hand, a more functional approach could start laying the groundwork to gradually solve this issue.

@ronnain I think you have solid points. I get you. But Angular is evolving all the time and I believe with new Service decorator (without having the all the baggage from Injectable) the team will be able to put more on to it and deliver new features and solutions. And I'm sure the community will provide articles, tutorials and all the rest of it.

As a wise man once said, we move forward only and march to victory 😂 this wise man also mentions defeat but no need for that for us.

https://youtu.be/Nx_EUwPiKIA?si=IVGIc3lItTz_Wvsc

@pullapprove pullapprove bot requested a review from AndrewKushnir April 14, 2026 22:56
Copy link
Copy Markdown
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed-for: public-api

@MunMunMiao
Copy link
Copy Markdown

I don’t think this would be a useful addition to Angular.
There are already ESLint rules encouraging the use of inject() instead of constructors.
Introducing a @service decorator would also make a large portion of existing articles, documentation, and videos—currently based on @Injectable({ providedIn: 'root' })—effectively outdated.
It could also create confusion for AI tools.
More importantly, I don’t see it bringing significant value.
One of the main issues with services, in my opinion, is injecting services that are not properly provided. This leads to runtime errors that could potentially be avoided.
I don’t think introducing a @service decorator would help address this problem.
On the other hand, a more functional approach could start laying the groundwork to gradually solve this issue.

@ronnain I think you have solid points. I get you. But Angular is evolving all the time and I believe with new Service decorator (without having the all the baggage from Injectable) the team will be able to put more on to it and deliver new features and solutions. And I'm sure the community will provide articles, tutorials and all the rest of it.

As a wise man once said, we move forward only and march to victory 😂 this wise man also mentions defeat but no need for that for us.

https://youtu.be/Nx_EUwPiKIA?si=IVGIc3lItTz_Wvsc

I believe that Angular needs to break free from the constraints of class now, rather than spending time on issues like Injectable and Service that do not affect the user experience.

In my opinion, this matter not only increases the cognitive load for users, but also makes it difficult for beginners to get started. They don't know what to use, and they will only find Angular hard to pick up

To describe this matter in Chinese, it's like “屎上雕花“

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer adev: preview area: core Issues related to the framework runtime detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.