Chapter 6: Services & Dependency Injection

Services & Dependency Injection!

This is where your app stops being a bunch of isolated components and starts acting like a real, organized system. Services are the place to put shared logic, state management (especially with signals), API calls, business rules, or anything that shouldn’t live inside a single component.

We’ll explain it like we’re pair-programming in VS Code at your desk in Airoli (it’s 1:45 PM IST, perfect chai time ☕). We’ll use modern Angular 19+ / 2025–2026 style: standalone components, inject() function (preferred over constructor injection), and signals inside services.

1. What is a Service? (Quick teacher explanation)

A service is just a plain TypeScript class decorated with @Injectable(). Its job:

  • Hold shared state (e.g., current user, shopping cart, theme)
  • Fetch/save data (HTTP, localStorage, IndexedDB)
  • Provide reusable functions (formatting dates, validation, calculations)
  • Act as a “single source of truth” for certain data across components

Services become powerful when combined with signals — you can have reactive, shared state without RxJS Subjects in most cases.

2. Creating an Injectable Service (Modern way)

Let’s create a real example: a UserService that manages the current logged-in user with signals.

Bash

(or manually create src/app/user.service.ts)

TypeScript

Notice:

  • We keep the writable signal private (_currentUser)
  • Expose readonly version + computed for safety & reactivity
  • Methods use .set() / .update() → any component reading currentUser() or isLoggedIn() auto-updates

3. providedIn: ‘root’ vs Component providers (Very important distinction!)

This is where most confusion happens — let’s compare clearly.

Option Scope / Lifetime Instance Count Best For When to Use in 2026
providedIn: ‘root’ Application-wide singleton 1 instance Global state (user, theme, config, API client) 90% of services — default recommendation
providedIn: ‘any’ One instance per lazy-loaded module Multiple Rare — optimization in large lazy apps Almost never now
providedIn: someModule One instance per module (legacy) Multiple Old NgModule projects Avoid in standalone
providers: [UserService] in component Instance per component + its children New per component Local state per route/component tree (e.g., form wizard state, tab-specific data) When you want isolation
providers: [{ provide: UserService, useClass: MockUserService }] Override for testing / subtree Varies Testing, feature flags, multi-tenant apps Advanced cases

Key teacher rule (2025–2026):

  • Start with providedIn: ‘root’ → it’s a singleton → shared state works perfectly with signals
  • Only move to component-level providers when you intentionally want separate instances (e.g., one shopping cart per tab, different form data per wizard step)

Example: Component-level provider (different instances)

TypeScript

→ Two <app-profile-editor> components would each have their own isolated UserService instance.

4. Injecting Services with inject() (The modern preferred way)

Since Angular 14+, and strongly recommended in 2025–2026 style guides:

  • Prefer inject() over constructor injection
  • Reasons: better readability, easier comments, better type inference, works great with class fields, inheritance-friendly, no constructor boilerplate

Old constructor style (still works, but less recommended now)

TypeScript

Modern inject() style

TypeScript

Template:

HTML

→ Because currentUser() and isLoggedIn() are signals (from service), the template auto-re-renders when you call login() or logout() — pure reactivity!

5. Mini Practice Task (build this now!)

  1. Use the UserService above (providedIn: ‘root’)
  2. Create two components:
    • app-header.component.ts → shows {{ userName() }} and login/logout button
    • app-dashboard.component.ts → shows isAdmin() and some admin-only content with @if
  3. Inject UserService with inject() in both
  4. Login from header → see dashboard auto-update (proves singleton + signal reactivity)

Bonus:

  • Try temporarily adding providers: [UserService] to one component → see how login in one place doesn’t affect the other (isolated instances)

Quick Rules of Thumb (pin on your wall)

  • Most services → @Injectable({ providedIn: ‘root’ }) + signals inside
  • Inject with inject() → cleaner, modern default
  • Expose readonly + computed → prevent accidental mutation from consumers
  • Component providers → only when you want per-instance state
  • Never mutate inputs/params directly — use service methods

You’ve now connected components with shared, reactive state — this is where Angular apps become truly powerful and maintainable.

Next chapter is usually Routing Basics (navigation, lazy loading, etc.). Ready to go there, or want to deepen services (e.g., HTTP integration with signals, effect() in services, multi-provider tokens, testing services)? Just tell me — we’re on a roll! 🚀

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *