Services and Dependency Injection
The React course needed Context (Intermediate Chapter 2) and eventually Zustand/Redux (Advanced Chapter 1) to share state across distant components without prop drilling. Angular has a single, built-in answer baked into the framework from the start: a service — an ordinary class holding shared logic or state — combined with dependency injection (DI), which hands that service to any component that asks for it. This is arguably Angular's defining feature.
A Service Is Just a Class
The @Injectable({ providedIn: 'root' }) decorator marks this class as something Angular's DI system can provide, and 'root' means a single shared instance exists for the entire app — every component that asks for CounterService gets that same one instance, making it a natural place to hold shared state. (A service can also be provided at a narrower scope, but app-wide 'root' is the common default.)
Injecting a Service Into a Component
inject(CounterService) asks Angular's DI system for the service — the component never creates it with new CounterService() itself. That distinction is the whole point of dependency injection: the component declares what it needs, and the framework supplies it, automatically handing over the same shared 'root' instance. Any other component injecting CounterService shares the exact same count, with no props, no Context provider, and nothing passed between them.
constructor(private counter: CounterService) {}. Both achieve identical results — the newer inject() function (used throughout this course) reads more cleanly and works in more places, but recognize the constructor form when you encounter it in existing codebases; it remains fully supported.
Why This Replaces Prop Drilling and Context
In React, sharing one piece of state between a header badge and a distant cart page required lifting state up and then either prop drilling or wrapping the tree in a Context provider (the exact journey the Shopping Cart project took). In Angular, both components simply inject() the same service — there's no provider to wrap anything in, no value object to memoize (Intermediate Chapter 7's concern), and no tree structure that the shared state has to flow through. The service exists independently of the component tree entirely.
Services for Logic, Not Just State
A service doesn't have to hold state — it's equally the home for shared behavior: logging, formatting, calculations, and (most importantly) talking to a backend API, which the HTTP client chapter builds on directly. Keeping that logic in an injectable service rather than inside components is a core Angular convention: components handle the view, services handle everything else.
counter.getCount() directly in a template works for state changed synchronously by user events (Angular re-checks the template after each event). But for state that changes asynchronously — a timer, an API response — exposing the value as an Observable and using the async pipe (Chapters 7 and 11), or Angular's newer signals (Chapter 13), is the more robust pattern. The plain-method approach here is the simplest starting point, deliberately, before those tools are introduced.
| Sharing need | React approach | Angular approach |
|---|---|---|
| Nearby components | Lift state up + props | A shared service (or @Input/@Output) |
| Distant components | Context, or Zustand/Redux | A service injected into both |
| Shared behavior/API calls | A custom hook / util module | An injectable service |
Coding Challenges
Build a CounterService (providedIn: 'root') holding a count with increment/decrement/reset methods, and inject it into a single component that displays and changes the count via buttons.
📄 View solutionInject the same CounterService into two completely separate, unrelated components — one that increments the count and one that only displays it — confirming both reflect the same shared value with nothing passed between them.
📄 View solutionBuild a stateless LoggerService with a log(message) method that prefixes a timestamp, and inject it into a component that calls it from a button click.
📄 View solutionChapter 6 Quick Reference
- A service — an ordinary class for shared state or behavior, marked
@Injectable({ providedIn: 'root' }) - providedIn: 'root' — one shared instance for the whole app
- inject(ServiceClass) — asks Angular's DI to supply the service; never
newit yourself - Two distant components injecting the same service share its state — no provider, no prop drilling, no Context
- Services are also the home for shared behavior: logging, formatting, and (next) API calls
- Older code injects via the constructor (
constructor(private x: X)) — equivalent toinject() - Next chapter: pipes — transforming displayed values in the template