intermediate20 min

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

AngularNot executed
This lab does not run in your browser or on VisaSparkSchools's servers. Read the code, predict what it does, then reveal the real expected output.

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

Knowledge check

1. What does @Injectable({ providedIn: 'root' }) register?
2. How does a component typically receive a service instance?
3. If two components inject the same providedIn: 'root' service, what do they share?

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.