skip to content

In Angular 22, what does the @Service() decorator do, and when should you still reach for @Injectable instead?

level: middleimportance: should knowfreq 42%

answer

  1. shorthand for a root singleton
  2. autoProvided defaults to true
  3. one factory option, no use* keys
  4. 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 lines
ts
import {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

for a junior

Know that @Service() is Angular 22's shorthand for a root-provided singleton service and that dependencies come from inject().

for a middle

Explain the factory and autoProvided options, the compile error for constructor parameters, and the cases where @Injectable is still required.

for a senior

Plan a gradual migration from @Injectable, use autoProvided: false for scoped state, and keep libraries on @Injectable when they must support pre-22 versions.

for a principal

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