skip to content

In Angular, how does a service declared with providedIn: 'root' differ from one listed in a component's providers array in instance count and lifetime?

level: juniorimportance: must knowfreq 76%

answer

  1. one for the app, one per host
  2. created on first injection
  3. destroyed with its owner
  4. shared state vs isolated state

basics

~20 s

providedIn: 'root' gives one application-wide instance, created lazily on first injection and kept until the app is destroyed. A component's providers array creates a separate instance for each component instance, visible to its subtree and destroyed with it.

solid answer

~50 s

`@Injectable({providedIn: 'root'})` — or, since v22, `@Service()` — registers the class with the root environment injector: the first `inject()` anywhere creates **one** instance that every consumer shares until the application is destroyed, and the class is tree-shaken away if nothing injects it. Listing the class in a component's `providers` creates a **new instance per component instance**; that component and its descendants share it, siblings and other copies of the component get their own, and it is destroyed — `ngOnDestroy` runs — when the component is. Pick root for shared app state and infrastructure, component providers for state that must be isolated per widget, such as one editor's undo history. Doing both for the same class is legal and gives the subtree its own copy, which is a frequent cause of 'my singleton lost its state'.

code

ts · 24 lines
ts
import {Component, Injectable, OnDestroy, inject, signal} from '@angular/core';

@Injectable({providedIn: 'root'}) // one instance for the whole app
export class NotificationCenter {
  readonly messages = signal<string[]>([]);
}

@Injectable() // no providedIn: must be listed in providers
export class EditHistory implements OnDestroy {
  readonly undoStack = signal<string[]>([]);
  ngOnDestroy() {
    this.undoStack.set([]);
  }
}

@Component({
  selector: 'app-editor',
  providers: [EditHistory], // one EditHistory per <app-editor>
  template: `<p>{{ history.undoStack().length }} edits</p>`,
})
export class Editor {
  history = inject(EditHistory);
  notifications = inject(NotificationCenter); // shared with every component
}

go deeper

for a junior

Recall that providedIn root means one shared instance for the app, and that component providers create one instance per component that dies with it.

for a middle

Explain lazy creation, tree-shaking, ngOnDestroy on component-scoped services, and what happens when a root service is also listed in a component's providers.

for a senior

Choose scope deliberately per piece of state, declare component-scoped classes without root provision so mistakes fail loudly, and spot accidental shadowing in review.

for a principal

Set team conventions for where state lives — root, route or component — so that ownership and lifetime of shared state stay predictable across features.

## Two scopes for the same class A **service** in Angular is usually a class that components obtain with `inject()`. Where it is **provided** decides how many instances exist and how long they live — the class itself does not change. | | `providedIn: 'root'` / `@Service()` | Component `providers: [X]` | |---|---|---| | Instances | One for the whole application | One per component instance | | Created | Lazily, on the first `inject()` | When first injected in that component's subtree | | Shared with | Every consumer in the app | The component and its descendants | | Destroyed | When the application is destroyed | With the component (its `ngOnDestroy` runs) | | Bundle | Tree-shaken if never injected | Always bundled with the component | | Typical use | Data access, auth, caches, app-wide state | Per-widget state, isolated form or editor state | ## Root scope ```ts @Injectable({providedIn: 'root'}) export class NotificationCenter {} ``` or, in Angular 22: ```ts @Service() export class NotificationCenter {} ``` Both put the class in the **root environment injector** without listing it anywhere. Key properties: - **Lazy creation**: nothing is constructed at bootstrap; the first injection builds the instance. - **Single instance**: every component, directive, guard or service that injects it gets the same object, so state written by one is seen by all. - **Tree-shakable**: because the class carries its own registration, a bundler can drop it when no code references it. ## Component scope ```ts @Component({selector: 'app-editor', providers: [EditHistory], template: '...'}) export class Editor { private history = inject(EditHistory); } ``` Each `<app-editor>` on the page gets its own `EditHistory`: - two editors side by side keep independent undo stacks; - children of an editor (a toolbar, a status bar) inject the same instance as their editor; - closing an editor destroys its instance, and `EditHistory.ngOnDestroy` can release resources. A class meant only for component scope is declared **without** automatic root provision — `@Injectable()` with no `providedIn`, or `@Service({autoProvided: false})` in v22 — so forgetting the `providers` entry fails loudly with NG0201 instead of silently falling back to a shared root instance. ## When both exist If a class is `providedIn: 'root'` **and** a component lists it in `providers`, that component's subtree gets its own copy, while everyone else shares root's. This is legal and occasionally deliberate (a sandboxed preview), but more often an accident: 1. a developer adds the service to a component's `providers` "to make it available"; 2. the component now writes to its private instance; 3. the rest of the app reads the root instance and never sees the change. ## Choosing - **Shared, app-lifetime state or stateless helpers** → root. - **State that must be isolated per instance of a widget** → component providers, with a non-auto-provided class. - **State shared by one feature area** → a route's providers, with the trade-offs of route-level instances. The instance count follows from where the provider sits, never from the class or from how it is injected. ## How interviewers probe it - **"Is a service a singleton?"** — only per injector that provides it. Root gives one per application; component providers give one per component instance. - **"Where do you put a service used by one screen only?"** — if the state must reset when the screen closes, component or route scope; if it is a stateless helper, root is still fine because it is tree-shakable and costs nothing unused. - **"What happens to subscriptions or timers in a component-scoped service?"** — implement `ngOnDestroy` (or use `DestroyRef`) in the service; it runs when the owning component is destroyed, which is the natural place to clean up. - **"Can two root services share state accidentally?"** — no, but a component that re-provides a root service splits state silently, which is the more common bug. A good answer names both numbers — one per app, one per component instance — and both lifetimes: application versus component.

  • When is a service provided in a component's providers array actually created?
    When something in that component's subtree first injects it, which for a service the component itself injects means during the component's creation. It is created once per component instance and cached in that component's element injector, and it is destroyed with the component, calling its ngOnDestroy.
  • Why declare a component-scoped service without providedIn: 'root'?
    So that a missing providers entry fails loudly. If the class were also root-provided, a component that forgot to list it would silently receive the shared root instance and leak state between widgets. Without automatic root provision, the lookup throws NG0201 and points at the mistake.

A root service is the office's shared printer: everyone uses the same one and it stays when people leave. A component-provided service is a notepad handed to each new desk: every desk gets its own, and it goes in the bin when the desk is cleared.

saying these in an interview costs you the question

  • providedIn: 'root' creates the service eagerly at bootstrap
  • Every component gets its own copy of a root service
  • Component-provided services survive after the component is destroyed
  • A class can only be provided in one place
  • Adding a root service to a component's providers reuses the root instance