The `@Injectable` decorator is your control panel for service lifecycle. It determines who can see your service and how long it lives in memory.
1The Power of 'root'
When you use providedIn: 'root', you are opting into Angular's modern DI system. This configuration makes the service a singleton that is lazily loaded only when needed. More importantly, it is 'tree-shakable'. If your build process discovers that no part of your code actually imports or uses that service, it will be completely excluded from the production JavaScript bundle, keeping your app light and fast.
2Hierarchical Injection
Angular's DI system is hierarchical. If you provide a service at the component level, that component and all of its children will share the same instance, but a different part of the app will get a separate instance if they also provide it. This is perfect for scenarios like a 'ChatComponent' where you want a local state that isn't leaked to other parts of the application, yet is shared among the sub-components of the chat feature.
3Step-by-Step Breakdown
The @Injectable() decorator is the marker that tells Angular this class can be used in the Dependency Injection system. Let's look at its configuration.
The most common configuration is 'providedIn: root'. This makes the service a Singleton available everywhere in the app.
Using 'providedIn: root' also enables Tree Shaking. If your app doesn't use the service, Angular's compiler will remove it from the final bundle!
Checkpoint: What is the primary benefit of using 'providedIn: root' for a service?
- βIt makes the service local to one component
- βIt creates a global singleton and enables tree-shaking
But what if you WANT multiple instances? You can provide a service at the component level using the 'providers' array.
Now, every time this component is created, it gets its OWN NEW instance of the service. It's not shared with the rest of the app.
Checkpoint: If you provide a service in the 'providers' array of a Component, is it still a global Singleton?
- βYes, it's still shared
- βNo, a new instance is created for that component tree
Understanding where to provide your services is key to managing your app's memory and state correctly. Great job!
Finally, let's explore the Singleton pattern in depth.
Level Up π
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1A Shared Focus-Management Service Is a Natural Injectable Use Case
Centralizing 'move focus to the newly opened modal' or 'return focus to the trigger element on close' logic in a single injectable service, rather than duplicating it per component, makes it far easier to keep focus behavior consistent across every modal, drawer, or dialog in the app.
2`@Injectable` Itself Has No Effect on the DOM or Accessibility Tree
Injectable services deal purely with logic and data, never markup β accessibility work always happens in a component's template, regardless of how many services that component injects or how they're scoped.
SEO Implications
- 1
Injectable Scope (`providedIn`) Has No Direct SEO Effect, But Affects SSR Correctness
A misconfigured `providedIn: 'root'` service holding user-specific or request-specific mutable state can leak data across concurrent requests under Angular Universal's server-side rendering β a correctness bug that indirectly threatens the reliability of the SSR content search engines actually crawl.
- 2
Tree-Shakeable Providers Keep the Client Bundle Lean
Services declared with `providedIn: 'root'` are tree-shakeable β if never injected anywhere, they're excluded from the final bundle entirely, keeping the JavaScript payload (and therefore Time to Interactive) smaller than manually registering every service in an `NgModule`'s providers array regardless of usage.
Best Practices
Default to `providedIn: 'root'` Unless You Have a Specific Reason for Narrower Scope
This gives you a tree-shakeable, app-wide singleton with the least amount of configuration β only reach for component-level `providers` when you deliberately want a fresh, isolated instance per component subtree.
Keep Injectable Services Focused on One Responsibility
A `UserService` that also handles unrelated logging, analytics, and caching concerns becomes hard to test and reason about β split genuinely distinct responsibilities into their own injectable services, even if they end up being used together.
Frequent Bugs
A service injected in two different components appears to hold two different, unsynchronized states.
The service is likely provided at the component level (in each component's own `providers` array) rather than `providedIn: 'root'` or a shared ancestor β each component subtree providing it independently creates its own separate instance, rather than sharing the intended single instance.
An injectable service works fine in the browser but throws errors during server-side rendering.
The service directly accesses browser-only globals (`window`, `document`, `localStorage`) in its constructor or methods without guarding for their absence β under Angular Universal's Node.js server environment, none of those exist. Guard with a platform check (`isPlatformBrowser`) before accessing them.
Real-World Examples
Shared Focus-Management Injectable
A single injectable service centralizes modal focus-management logic β moving focus in on open, returning it to the trigger element on close β so every modal component in the app gets consistent, correct behavior for free.
@Injectable({ providedIn: 'root' })
export class FocusManagerService {
private lastFocused?: HTMLElement;
trapFocus(modal: HTMLElement) {
this.lastFocused = document.activeElement as HTMLElement;
modal.focus();
}
restoreFocus() { this.lastFocused?.focus(); }
}