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.
What is an Angular service, how does DI provide it (providedIn/providers), and why do teams use services?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
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 |
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 |
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 |
|---|---|---|
| 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 | New instance per component instance (and its subtree) | Per-screen/wizard state that must reset when component is destroyed |
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;
}
}
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.
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 |
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.