Services and Dependency Injection
Sharing logic and state across components with an injectable service.
What you'll learn
- Declare a service with @Injectable() and understand what providedIn: 'root' means
- Inject a service into a component's constructor
- Explain why services (not components) are the right place for shared state/logic
Explanation
A service is a plain TypeScript class marked @Injectable(), used to hold logic or state that doesn't belong to any single component -- shared data, API calls, business logic. @Injectable({ providedIn: "root" }) registers the service as a single, shared (singleton) instance available application-wide, without needing to manually wire it up anywhere else.
Components receive a service through dependency injection, typically via the constructor: constructor(private cartService: CartService) {} -- Angular's injector sees the type annotation, finds (or creates) the right instance, and passes it in automatically. The component never has to write new CartService() itself.
This matters for a real reason: if two components both inject the same providedIn: "root" service, they share the same instance -- so if one component adds an item via the service, the other component (reading from that same service) sees the update too. This is the standard Angular pattern for sharing state across otherwise-unrelated components, instead of passing everything through a long chain of @Input/@Output bindings.
Guided lab
Predict: Two components sharing one injected service instance
This models providedIn: 'root' by manually sharing one CartService instance between two components (a live DI container can't run here). Predict what the second component sees.
class CartService {
private items: string[] = [];
add(item: string): void {
this.items.push(item);
}
getItems(): string[] {
return this.items;
}
}
// Modeling Angular's singleton providedIn: "root" behavior: both
// components are constructed with the SAME CartService instance.
const sharedCart = new CartService();
class ProductPageComponent {
constructor(private cart: CartService) {}
addToCart(item: string): void {
this.cart.add(item);
}
}
class CartSummaryComponent {
constructor(private cart: CartService) {}
itemCount(): number {
return this.cart.getItems().length;
}
}
const productPage = new ProductPageComponent(sharedCart);
const cartSummary = new CartSummaryComponent(sharedCart);
productPage.addToCart("Keyboard");
productPage.addToCart("Mouse");
console.log(cartSummary.itemCount());Stuck? Get a hint.
Common mistakes
- Manually instantiating a service with new ServiceName() instead of injecting it via the constructor, which creates a separate instance instead of sharing the app-wide singleton.
- Putting genuinely shared application state directly in a component instead of a service, making it hard for sibling/unrelated components to access.
- Forgetting @Injectable({ providedIn: 'root' }) (or an equivalent registration) means Angular's injector doesn't know how to create the service.
Knowledge check
Takeaway
Put shared logic/state in an @Injectable() service and receive it via constructor injection -- never instantiate it manually with new.
Summary
Services (@Injectable, often providedIn: 'root') hold shared logic/state; components receive them via constructor-based dependency injection, sharing one singleton instance.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.