Angular Services and DI
Angular services encapsulate reusable logic and shared state, and the framework's built-in dependency injection (DI) system hands an instance to any component, directive, or service that asks for one.
What Is a Service?
A service is just a plain TypeScript class with a focused job: fetching data, talking to a browser API, formatting values, or holding state that multiple components need to share. Services keep components thin — a component should describe what the user sees and does, not how data is fetched or transformed.
Creating an Injectable Service
Decorating a class with @Injectable tells Angular's compiler how to construct it and what to inject into its own constructor. The providedIn: 'root' option registers the service with the application's root injector, so it becomes an app-wide singleton that is also tree-shakeable if nothing ever imports it.
logger.service.ts
import { Component, Injectable } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
private logs: string[] = [];
log(message: string): void {
const entry = `[${new Date().toISOString()}] ${message}`;
this.logs.push(entry);
console.log(entry);
}
getHistory(): string[] {
return [...this.logs];
}
}
@Component({
selector: 'app-root',
standalone: true,
template: `
<button (click)="logMessage()">Log a message</button>
<p>History entries: {{ logger.getHistory().length }}</p>
`
})
export class AppComponent {
constructor(private logger: LoggerService) {
this.logger.log('AppComponent created');
}
logMessage(): void {
this.logger.log('Button clicked');
}
}
bootstrapApplication(AppComponent).catch((err) => console.error(err));
Constructor Injection
The classic way to receive a service is to declare it as a constructor parameter with an access modifier such as private or protected. Angular's injector inspects the constructor's type metadata, finds (or creates) a matching instance, and passes it in automatically — you never call 'new LoggerService()' yourself.
greeting.component.ts
import { Component, Injectable } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
private logs: string[] = [];
log(message: string): void {
const entry = `[${new Date().toISOString()}] ${message}`;
this.logs.push(entry);
console.log(entry);
}
getHistory(): string[] {
return [...this.logs];
}
}
@Component({
selector: 'app-greeting',
standalone: true,
template: `
<button (click)="greet()">Say Hello</button>
`
})
export class GreetingComponent {
constructor(private logger: LoggerService) {}
greet(): void {
this.logger.log('Greeting button clicked');
}
}
bootstrapApplication(GreetingComponent).catch((err) => console.error(err));
- Testability — a component can be unit-tested with a fake/mock service instead of the real one.
- Singletons by default — providedIn: 'root' gives every consumer the same instance and the same state.
- Loose coupling — components depend on an interface-like contract, not on how data is actually fetched.
- Swappable implementations — you can provide a different class for the same token in tests or per-environment builds.
Provider Scopes
Where you register a service controls its lifetime and how many instances exist. Root-level registration is the most common choice, but component-level providers create a fresh instance per component subtree, which is useful for state that should reset when a feature is closed.
cart.service.ts
import { Injectable, inject, Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Injectable({ providedIn: 'root' })
export class CartService {
private items: string[] = [];
addItem(name: string): void {
this.items.push(name);
}
get itemCount(): number {
return this.items.length;
}
}
@Component({
selector: 'app-cart-badge',
standalone: true,
template: `<span>Items: {{ cart.itemCount }}</span>`
})
export class CartBadgeComponent {
protected cart = inject(CartService);
}
bootstrapApplication(CartBadgeComponent).catch((err) => console.error(err));
Exercise: Angular Services and DI
Which decorator marks a class as available for dependency injection?