Description
Angular is introducing a new @Service() decorator for dependency injection services.
It would be useful for angular-eslint to support this decorator as a first-class Angular decorator and optionally provide a rule that prefers @Service() over @Injectable({providedIn: 'root'}) when the migration is safe.
Related Angular PR: angular/angular#68506
Motivation
Today, root-provided Angular services are commonly declared with @Injectable({providedIn: 'root'}):
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'root'})
export class UserService {}
With the new @Service() decorator, the same root-provided service can be expressed more directly:
import {Service} from '@angular/core';
@Service()
export class UserService {}
Angular itself has started using @Service() in places where @Injectable({providedIn: 'root'}) was previously used.
For factory-based services, the Angular PR also shows this migration pattern:
@Injectable({
providedIn: 'root',
useFactory: () => new NgLocaleLocalization(inject(LOCALE_ID)),
})
to:
@Service({
factory: () => new NgLocaleLocalization(inject(LOCALE_ID)),
})
Proposed rule
Add a new rule that reports @Injectable({providedIn: 'root'}) when it can be safely replaced with @Service().
Possible rule names:
prefer-service-decorator
prefer-angular-service
prefer-service-over-injectable
I personally prefer prefer-service-decorator, because it is short and directly describes the desired Angular decorator.
Invalid examples
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'root'})
export class UserService {}
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {}
import {Injectable, inject} from '@angular/core';
@Injectable({
providedIn: 'root',
useFactory: () => inject(UserServiceImpl),
})
export abstract class UserService {}
Valid examples
import {Service} from '@angular/core';
@Service()
export class UserService {}
import {Service, inject} from '@angular/core';
@Service({
factory: () => inject(UserServiceImpl),
})
export abstract class UserService {}
import {Injectable} from '@angular/core';
@Injectable()
export class LocallyProvidedService {}
import {Injectable} from '@angular/core';
@Injectable({providedIn: SomeModule})
export class ModuleScopedService {}
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'platform'})
export class PlatformService {}
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'any'})
export class AnyScopedService {}
Auto-fix
The rule could provide an auto-fix for simple and safe cases.
Basic case
Before:
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'root'})
export class UserService {}
After:
import {Service} from '@angular/core';
@Service()
export class UserService {}
Factory case
Before:
import {Injectable, inject} from '@angular/core';
@Injectable({
providedIn: 'root',
useFactory: () => inject(UserServiceImpl),
})
export abstract class UserService {}
After:
import {Service, inject} from '@angular/core';
@Service({
factory: () => inject(UserServiceImpl),
})
export abstract class UserService {}
Conservative auto-fix conditions
The auto-fix should probably be conservative at first.
It can safely fix:
@Injectable({providedIn: 'root'})
@Injectable({ providedIn: 'root' })
@Injectable({providedIn: 'root', useFactory: ...})
It should not auto-fix yet:
@Injectable()
@Injectable({providedIn: null})
@Injectable({providedIn: 'platform'})
@Injectable({providedIn: 'any'})
@Injectable({providedIn: SomeModule})
@Injectable({useClass: ...})
@Injectable({useExisting: ...})
@Injectable({useValue: ...})
- cases where the migration semantics are not confirmed by Angular yet
Suggested configuration
{
"@angular-eslint/prefer-service-decorator": "error"
}
or with options:
{
"@angular-eslint/prefer-service-decorator": [
"error",
{
"fixFactories": true
}
]
}
Why this belongs in angular-eslint
angular-eslint already understands Angular decorators and provides rules around Angular-specific class metadata.
Since @Service() is a new Angular DI decorator, angular-eslint should recognize it as a first-class Angular decorator and help teams migrate when the migration is safe.
This would also help avoid custom project-level lint rules once @Service() becomes the recommended style for root-provided services.
Versioning / stability note
This request depends on the Angular @Service() API becoming publicly available.
Until the Angular PR lands, this rule could be experimental or version-gated.
Description
Angular is introducing a new
@Service()decorator for dependency injection services.It would be useful for
angular-eslintto support this decorator as a first-class Angular decorator and optionally provide a rule that prefers@Service()over@Injectable({providedIn: 'root'})when the migration is safe.Related Angular PR: angular/angular#68506
Motivation
Today, root-provided Angular services are commonly declared with
@Injectable({providedIn: 'root'}):With the new
@Service()decorator, the same root-provided service can be expressed more directly:Angular itself has started using
@Service()in places where@Injectable({providedIn: 'root'})was previously used.For factory-based services, the Angular PR also shows this migration pattern:
to:
Proposed rule
Add a new rule that reports
@Injectable({providedIn: 'root'})when it can be safely replaced with@Service().Possible rule names:
prefer-service-decoratorprefer-angular-serviceprefer-service-over-injectableI personally prefer
prefer-service-decorator, because it is short and directly describes the desired Angular decorator.Invalid examples
Valid examples
Auto-fix
The rule could provide an auto-fix for simple and safe cases.
Basic case
Before:
After:
Factory case
Before:
After:
Conservative auto-fix conditions
The auto-fix should probably be conservative at first.
It can safely fix:
@Injectable({providedIn: 'root'})@Injectable({ providedIn: 'root' })@Injectable({providedIn: 'root', useFactory: ...})It should not auto-fix yet:
@Injectable()@Injectable({providedIn: null})@Injectable({providedIn: 'platform'})@Injectable({providedIn: 'any'})@Injectable({providedIn: SomeModule})@Injectable({useClass: ...})@Injectable({useExisting: ...})@Injectable({useValue: ...})Suggested configuration
{ "@angular-eslint/prefer-service-decorator": "error" }or with options:
{ "@angular-eslint/prefer-service-decorator": [ "error", { "fixFactories": true } ] }Why this belongs in angular-eslint
angular-eslintalready understands Angular decorators and provides rules around Angular-specific class metadata.Since
@Service()is a new Angular DI decorator,angular-eslintshould recognize it as a first-class Angular decorator and help teams migrate when the migration is safe.This would also help avoid custom project-level lint rules once
@Service()becomes the recommended style for root-provided services.Versioning / stability note
This request depends on the Angular
@Service()API becoming publicly available.Until the Angular PR lands, this rule could be experimental or version-gated.