What is an Angular service, how does DI provide it (providedIn/providers), and why do teams use services?

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

In Angular, a “service” is just a class that becomes useful when it’s provided through Angular’s dependency injection (DI). Services are used to centralize reusable logic and side effects (HTTP, caching, orchestration, shared state) so components stay focused on UI + event wiring. Service scope is controlled by where you provide it (root/module/component), which determines lifetime and instance sharing.

Answer

Core idea

An Angular service is typically a plain TypeScript class that you register with Angular’s dependency injection system. DI creates the instance and injects it where needed. The value is not the class itself — it’s the lifetime + sharing + testability you get from DI and a clean separation from UI code.

What it is

What it is NOT

A DI-provided class used by components/directives/other services

A special Angular-only construct (it’s still “just a class”)

A place for reusable logic + side effects (HTTP, caching, orchestration)

A place for DOM manipulation (that belongs in components/directives)

A unit that’s easy to mock in tests

A global mutable “god object” used everywhere without boundaries

Interview framing: define service via DI + responsibilities

Why teams use services

To keep components thin and predictable: components render + translate UI events into intent; services own reusable logic and side effects. This improves reuse, testability, and maintainability in large apps.

Typical responsibility

Why it belongs in a service

HTTP/data access (repositories)

Centralizes endpoints, retries, error mapping, DTO→domain mapping

Caching + request de-duplication

Prevents duplicate calls across multiple consumers

Cross-component state (facade/store wrapper)

Makes sharing state explicit; reduces tight coupling between components

Business rules / normalization

One source of truth; easier to unit test than template/component code

App orchestration (multi-step flows)

Keeps complex flows out of UI layer; promotes clean boundaries

What interviewers expect: services for side effects + shared logic

How a service gets an instance: providers and scope

Service lifetime is determined by where it’s provided. Angular DI is hierarchical: child injectors can override parent providers.

Provide it where

Instance lifetime

When to use

@Injectable({ providedIn: 'root' })

Singleton for the app (tree-shakeable provider)

Most services (API clients, facades, shared utilities)

Feature/module providers (module-based apps)

Usually singleton per module injector (depending on module loading)

Legacy module setups; some library patterns

Component providers: [...]

New instance per component instance (and its subtree)

Per-screen/wizard state that must reset when component is destroyed

Scope is an interview hot-spot: root vs component providers
TYPESCRIPT
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, map, of, shareReplay } from 'rxjs';

type UserDto = { id: string; name: string };
export type User = { id: string; displayName: string };

@Injectable({ providedIn: 'root' })
export class UsersService {
  private readonly apiUrl = '/api/users';
  private users$?: Observable<User[]>;

  constructor(private http: HttpClient) {}

  /**
   * Cache + dedupe: multiple subscribers share one request.
   * If you need refresh, expose an explicit invalidation method.
   */
  getUsers(): Observable<User[]> {
    this.users$ ??= this.http.get<UserDto[]>(this.apiUrl).pipe(
      map(dtos => dtos.map(d => ({ id: d.id, displayName: d.name })) as User),
      // In real apps, prefer a typed error strategy (domain errors) over swallowing.
      catchError(() => of([])),
      shareReplay({ bufferSize: 1, refCount: true })
    );

    return this.users$;
  }

  invalidateUsersCache(): void {
    this.users$ = undefined;
  }
}
                  
TYPESCRIPT
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { AsyncPipe, NgFor } from '@angular/common';
import { UsersService } from './users.service';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [NgFor, AsyncPipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <button type="button" (click)="refresh()">Refresh</button>

    <ul>
      <li *ngFor="let u of (users$ | async)">
        {{ u.displayName }}
      </li>
    </ul>
  `
})
export class UsersComponent {
  private readonly usersService = inject(UsersService);
  readonly users$ = this.usersService.getUsers();

  refresh(): void {
    this.usersService.invalidateUsersCache();
    // reassign stream (simple pattern); in larger apps use a facade/store.
    (this as any).users$ = this.usersService.getUsers();
  }
}
                  

Component-scoped service example (per-instance state)

If you need state that resets when a component is destroyed (wizard/session), provide the service at the component level.

TYPESCRIPT
import { Injectable } from '@angular/core';

@Injectable()
export class WizardStateService {
  step = 1;
  data: Record<string, unknown> = {};

  reset(): void {
    this.step = 1;
    this.data = {};
  }
}

// component.ts
// @Component({
//   ...
//   providers: [WizardStateService]
// })
// export class CheckoutWizardComponent {
//   constructor(public wizard: WizardStateService) {}
// }
                  

Common pitfall

What breaks

Fix

Putting HttpClient calls in components

Duplicated logic, harder tests, messy lifecycles

Move data access + mapping + error policy into a service/facade

Using a root singleton as a dumping ground for random mutable state

Hidden coupling and hard-to-debug state leaks across screens

Keep state ownership explicit (facade/store); use component-scoped services for per-screen state

Services touching DOM directly

SSR/testing issues; breaks separation of concerns

DOM work belongs in components/directives (Renderer2 if needed)

Subjects never completed / manual subscriptions everywhere

Leaks and unpredictable behavior

Prefer async pipe, takeUntilDestroyed(), or centralized streams in facades

Pitfalls seniors mention quickly
Summary

Angular services are DI-provided classes used to centralize reusable logic and side effects. The key interview points are: (1) services are “just classes” + DI providers, (2) scope/lifetime depends on where you provide them (root vs component), and (3) services keep components focused on UI, improving reuse and testability.

Similar questions
Guides
27 / 37