In Angular 22, what does the @Service() decorator do, and when should you still reach for @Injectable instead?
answer
- shorthand for a root singleton
- autoProvided defaults to true
- one factory option, no use* keys
- inject() only, no constructor parameters
basics
~20 s@Service() marks a class as a tree-shakable root singleton, shorthand for @Injectable({providedIn: 'root'}). It supports a factory option and autoProvided: false, but no constructor injection, useClass-style keys or platform scope — use @Injectable for those.
solid answer
~40 s`@Service()`, new in Angular 22, is the ergonomic shorthand for `@Injectable({providedIn: 'root'})`: the class is auto-provided in the root injector, created lazily and tree-shaken if never injected. Its options are deliberately small: `factory: () => ...` replaces the created value (running in an injection context, so `inject()` works inside), and `autoProvided: false` turns off root provision so you list the class in a component's or route's `providers` yourself. It only supports `inject()` for dependencies — a constructor with parameters is a compile error ('@Service class cannot use constructor dependency injection'). Keep `@Injectable` for constructor injection, for `useClass`/`useValue`/`useExisting`/`useFactory` recipes, and for non-root scopes such as `providedIn: 'platform'`.
code
ts · 26 linesimport {InjectionToken, Service, inject} from '@angular/core';
export const FLAGS_ENDPOINT = new InjectionToken<string | null>('FLAGS_ENDPOINT', {
providedIn: 'root',
factory: () => null,
});
@Service({
// runs in an injection context: inject() is allowed here
factory: () => (inject(FLAGS_ENDPOINT) ? new RemoteFlags() : new FeatureFlags()),
})
export class FeatureFlags {
isOn(flag: string): boolean {
return false;
}
}
class RemoteFlags extends FeatureFlags {
private endpoint = inject(FLAGS_ENDPOINT);
override isOn(flag: string): boolean {
return this.endpoint !== null && flag.length > 0;
}
}
@Service({autoProvided: false}) // add to a component's or route's providers yourself
export class WizardDraft {}go deeper
Know that @Service() is Angular 22's shorthand for a root-provided singleton service and that dependencies come from inject().
Explain the factory and autoProvided options, the compile error for constructor parameters, and the cases where @Injectable is still required.
Plan a gradual migration from @Injectable, use autoProvided: false for scoped state, and keep libraries on @Injectable when they must support pre-22 versions.
Decide a codebase-wide convention for new services and a migration pace, weighing consistency against churn in stable code that uses constructor injection.
## What `@Service()` is Angular 22 added `@Service()` to `@angular/core` as the recommended way to declare an application service. The docs describe it as a modern shorthand for `@Injectable({providedIn: 'root'})`: ```ts import {Service} from '@angular/core'; @Service() export class FeatureFlags { isOn(flag: string) { return false; } } ``` This class is: - **auto-provided** in the root environment injector — no `providers` entry needed; - a **singleton** for the application, created on first injection; - **tree-shakable** — dropped from the bundle if nothing injects it. Under the hood the compiler emits the same kind of injectable definition as `providedIn: 'root'`, with `providedIn` set to `root` unless `autoProvided` is `false`. ## Its options | Option | Effect | |---|---| | none | Root singleton, class constructed with `new` | | `factory: () => T` | Root singleton whose value is whatever the factory returns; the factory runs in an injection context | | `autoProvided: false` | Not provided anywhere automatically; you add the class to a component's or route's `providers` | `factory` covers the cases where you want to decide the implementation at runtime, for example returning a no-op subclass unless a configuration token enables the real one. It replaces the `useClass`, `useValue`, `useExisting` and `useFactory` keys of `@Injectable` — there is exactly one way to customise creation. `autoProvided: false` is the v22 way to write a service meant for component or route scope. Missing the `providers` entry then fails loudly with NG0201 instead of silently falling back to a root instance. ## What it deliberately does not do 1. **No constructor injection.** Dependencies must come from `inject()` in field initializers or the constructor body. A constructor that declares parameters is rejected at compile time: `@Service class cannot use constructor dependency injection. Use the inject function instead.` The check also looks at inherited constructors. 2. **No provider recipes.** No `useClass`, `useValue`, `useExisting` or `useFactory` in the decorator. 3. **No non-root scopes.** No `providedIn: 'platform'` and no deprecated `'any'` or NgModule scopes. ## When to keep `@Injectable` - A class written with constructor parameters that you are not migrating yet. - A provider that needs `useClass` or another recipe in its own metadata. - A platform-scoped service shared by several applications on one page. - Code that must compile against Angular versions before 22, such as a library with a wide peer-dependency range. `@Injectable` is not deprecated; both decorators coexist, and a codebase can migrate class by class. ## Practical guidance - New app services that use `inject()`: `@Service()`. - New per-component or per-route state: `@Service({autoProvided: false})` plus a `providers` entry where it belongs. - Runtime choice of implementation: `@Service({factory: ...})`. - Anything else: `@Injectable`, as before. ## Migrating an existing service A typical conversion looks like this: 1. Replace constructor parameters with `inject()` field initializers — `constructor(private http: HttpClient)` becomes `private http = inject(HttpClient);`. 2. Swap `@Injectable({providedIn: 'root'})` for `@Service()`. 3. For classes that had no `providedIn` and are listed in component or route `providers`, use `@Service({autoProvided: false})` and keep the `providers` entries. 4. Leave classes that rely on `useClass`-style metadata or platform scope on `@Injectable`. Behaviour does not change: the instance count, the lifetime and the tree-shaking stay the same, which is why the migration can proceed file by file. In an interview, the precise answer is that `@Service()` changes the **ergonomics and defaults**, not the DI model: the same root injector, the same lazy instantiation, the same tree-shaking.
- What happens if a @Service() class declares constructor parameters?The Angular compiler reports an error: '@Service class cannot use constructor dependency injection. Use the inject function instead.' It checks inherited constructors too. Move each dependency into a field initializer such as `private http = inject(HttpClient)`, or keep the class on `@Injectable` until you migrate it.
- Is @Injectable deprecated in Angular 22?No. `@Service()` is the recommended shorthand for new root singletons that use `inject()`, while `@Injectable` remains for constructor injection, provider recipes such as `useClass`, and non-root scopes like `providedIn: 'platform'`. Both coexist and produce the same kind of injectable definition.
saying these in an interview costs you the question
- @Service() creates a new instance per component
- @Service() replaces and deprecates @Injectable
- @Service() supports useClass and useValue options
- @Service() classes can use constructor parameter injection
- autoProvided: false makes the service eager instead of lazy