The singleton pattern is the 'Superpower' of Angular. It allows you to create a centralized state that acts as the single source of truth for your entire application.
1State Persistence
One of the biggest challenges in SPA (Single Page Application) development is keeping data alive while the user moves around. Since components are destroyed and recreated during navigation, you cannot store long-term data inside them. Singleton services live as long as the application is running. By moving your application state into a service, you ensure that the user's progress is never lost as they move through your routes.
2The Observable Service Pattern
While a simple property in a service can store data, it isn't 'reactive'. Components would have to constantly poll the service to see if the value changed. By using a BehaviorSubject, you turn your singleton into a broadcaster. Components 'subscribe' to the data stream and are automatically notified (and updated) the millisecond the service data changes. This is the foundation of high-performance Angular UI.
3Step-by-Step Breakdown
Singletons are the backbone of Angular state management. A singleton is a service where exactly one instance exists for the entire application.
Because it's a singleton, the data inside the service persists even when you navigate between different pages/routes.
Let's see it in action. Component A updates a 'counter' in the service. Component B, on a different page, sees the updated value.
Checkpoint: If a service is a Singleton, what happens to its data when the user navigates to a new route?
- βThe data is reset to its initial state
- βThe data persists and is still available
This makes singletons perfect for storing things like: User Authentication status, Shopping Cart items, or Theme preferences.
To make a singleton truly reactive, we often combine it with RxJS Subjects. This allows components to 'listen' for changes in real-time.
Checkpoint: Which RxJS class is commonly used in singleton services to broadcast state changes to subscribers?
- βBasic Observable
- βBehaviorSubject
Magnificent! You've mastered the heart of Angular's architecture. Your apps are now connected, reactive, and efficient.
Congratulations! You've finished the Services and DI chapter. Next, we'll dive into the world of Routing!
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 Singleton State Store Is an Ideal Home for App-Wide Accessibility Preferences
Something like a user's 'reduce motion' or 'high contrast' preference is naturally app-wide state β a singleton service is exactly the right pattern to store it once and let every component reactively respect it, rather than each component re-reading a media query independently.
2A Singleton's Shared State Changing Should Trigger Announcements Where Relevant
If a singleton service's state change should be perceivable (a global 'unsaved changes' flag, a notification count), pair the state update with an explicit `aria-live` announcement in whichever component surfaces that change, since the store itself has no way to speak to the user directly.
SEO Implications
- 1
A Root-Provided Singleton Is Tree-Shakeable if Never Actually Injected Anywhere
`providedIn: 'root'` services are only included in the final bundle if something actually injects them β an unused singleton service is automatically excluded, keeping bundle size (and therefore Time to Interactive) as small as the app's actual dependency graph requires.
- 2
Singleton State Holding Per-Request Data Is a Correctness Risk Specifically Under SSR
A single Node.js process handling Angular Universal's server-side rendering can serve multiple concurrent users' requests β a singleton holding mutable per-user state can leak between them unless carefully scoped, a bug class that simply can't happen in a purely client-side single-user browser context.
Best Practices
Back Singleton State With a `BehaviorSubject`, Not a Plain Property
A `BehaviorSubject` immediately gives late subscribers the current value on subscription and lets every consumer reactively receive future updates β a plain property offers neither, forcing consumers to manually re-check it or miss updates entirely.
Expose Only a Read-Only Observable Publicly, Keep the Mutable Subject Private
Exposing `cartCount$ = this.count.asObservable()` (read-only) while keeping the underlying `BehaviorSubject` private forces all state changes to go through the service's own explicit methods, preventing external code from directly pushing arbitrary values into the shared state.
Frequent Bugs
A singleton service's state appears to reset unexpectedly when navigating to a lazy-loaded feature module.
The service is being re-provided in the lazy-loaded module's own `providers` array, creating a second, separate instance scoped to that module's child injector rather than sharing the app-wide singleton β remove the duplicate provider declaration so the lazy module resolves the existing root instance instead.
External code can push arbitrary invalid values directly into what's supposed to be centrally-managed shared state.
The service exposed its internal `BehaviorSubject` directly rather than a read-only `Observable` derived from it β any consumer with a reference to the raw Subject can call `.next()` on it themselves, bypassing any validation logic the service's own methods were meant to enforce. Expose only `.asObservable()` publicly and keep the mutable Subject private.
Real-World Examples
App-Wide Accessibility Preference Store
A singleton service centralizes the user's 'reduce motion' preference, read once from system settings or user choice, letting every component across the app reactively respect it without independently querying media features.
@Injectable({ providedIn: 'root' })
export class PreferencesService {
private reduceMotion = new BehaviorSubject(matchMedia('(prefers-reduced-motion: reduce)').matches);
reduceMotion$ = this.reduceMotion.asObservable();
}