Angular Services Explained — Share Data and Logic Across Components
In this tutorial, you will learn about Angular Services Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
Angular services are singleton classes that provide reusable functionality across your application, from data fetching and state management to logging and business logic.
What You'll Learn
- What services are and why they matter for code organization
- How to create a service with
@Injectable - How to inject services into components
- How to share data between components using services
- How to make HTTP requests in services
Why It Matters
Services prevent code duplication by centralizing shared logic. Instead of each component fetching its own data, a single service handles HTTP calls, caching, and error handling. This makes your app easier to test, maintain, and scale.
Real-World Use
Durga Antivirus Pro uses a ThreatDetectionService that fetches threat signatures, checks files against them, and logs results. Multiple dashboard components use this service to display scan status, threat counts, and historical data without duplicating the detection logic.
flowchart TD
A[Component A] -->|injects| B[DataService]
C[Component B] -->|injects| B
D[Component C] -->|injects| B
B -->|HTTP| E[API Server]
B -->|Shared State| F[All Components]
style B fill:#f97316,color:#fff
Creating a Service
A service is a plain TypeScript class with the @Injectable decorator:
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root"
})
export class UserPreferencesService {
private theme: "light" | "dark" = "light";
private fontSize = 16;
getTheme() {
return this.theme;
}
setTheme(theme: "light" | "dark") {
this.theme = theme;
}
getFontSize() {
return this.fontSize;
}
setFontSize(size: number) {
this.fontSize = size;
}
}
Expected output: A service that stores and retrieves user preferences with no component needed to instantiate it.
providedIn: "root" tells Angular to create a single instance of the service for the entire application. Angular creates it lazily when it is first injected and destroys it when the app shuts down.
Injecting a Service into a Component
Use constructor injection to receive a service instance:
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { UserPreferencesService } from "./user-preferences.service";
@Component({
selector: "app-settings-panel",
standalone: true,
imports: [CommonModule],
template: `
<div [style.font-size.px]="prefs.getFontSize()">
<label>
Theme:
<select [value]="prefs.getTheme()" (change)="onThemeChange($event)">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label>
Font Size:
<input type="range" min="12" max="24"
[value]="prefs.getFontSize()"
(input)="onFontChange($event)" />
</label>
</div>
`
})
export class SettingsPanelComponent {
constructor(public prefs: UserPreferencesService) {}
onThemeChange(event: Event) {
const value = (event.target as HTMLSelectElement).value as "light" | "dark";
this.prefs.setTheme(value);
}
onFontChange(event: Event) {
const value = Number((event.target as HTMLInputElement).value);
this.prefs.setFontSize(value);
}
}
Expected output: A settings panel that modifies and reads preferences through a shared service.
Angular's Dependency Injection system resolves the service automatically. You do not need to call new UserPreferencesService(). Angular creates the instance and passes it to the constructor.
Sharing Data Between Components
Services can hold shared state that multiple components read and write:
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
export interface Notification {
id: number;
message: string;
type: "info" | "warning" | "error";
read: boolean;
}
@Injectable({
providedIn: "root"
})
export class NotificationService {
private notifications = new BehaviorSubject<Notification[]>([
{ id: 1, message: "Scan complete", type: "info", read: false },
{ id: 2, message: "Threat detected", type: "error", read: false },
]);
getNotifications(): Observable<Notification[]> {
return this.notifications.asObservable();
}
addNotification(notification: Notification) {
this.notifications.next([...this.notifications.getValue(), notification]);
}
markAsRead(id: number) {
const updated = this.notifications.getValue().map(n =>
n.id === id ? { ...n, read: true } : n
);
this.notifications.next(updated);
}
}
One component displays the notification count, another shows the list:
@Component({
selector: "app-notification-badge",
standalone: true,
imports: [CommonModule],
template: `
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
`
})
export class NotificationBadgeComponent implements OnInit {
unreadCount = 0;
constructor(private notificationService: NotificationService) {}
ngOnInit() {
this.notificationService.getNotifications().subscribe(notifications => {
this.unreadCount = notifications.filter(n => !n.read).length;
});
}
}
Expected output: A badge showing the count of unread notifications. When a notification is marked as read, the count updates automatically.
Using BehaviorSubject ensures that new subscribers receive the latest value immediately and future updates automatically.
HTTP Requests in Services
Services are the natural place to make HTTP calls:
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: "root"
})
export class UserService {
private apiUrl = "https://jsonplaceholder.typicode.com/users";
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: Omit<User, "id">): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
}
The component subscribes to the service's methods:
@Component({
selector: "app-user-list",
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="loading">Loading users...</div>
<ul *ngIf="!loading">
<li *ngFor="let user of users">{{ user.name }} - {{ user.email }}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
loading = true;
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getUsers().subscribe({
next: data => {
this.users = data;
this.loading = false;
},
error: err => {
console.error("Failed to load users", err);
this.loading = false;
}
});
}
}
Expected output: A list of users fetched from the API, with a loading state while the request is in flight.
The HttpClient is provided by provideHttpClient() in the app config. Services abstract the HTTP layer so components do not need to know how data is fetched.
Common Mistakes
Creating multiple instances of a service — If you provide a service in a component's
providersarray instead ofroot, each component gets a new instance with separate state.Not unsubscribing from service observables — Services often expose observables. Components that subscribe should use the async pipe or unsubscribe in
ngOnDestroy.Putting HTTP calls directly in components — Direct HTTP calls in components make testing harder and violate Separation Of Concerns.
Mutating service state directly from components — Components should call service methods to change state, not directly modify service properties.
Overusing services for component-only state — If only one component uses the data, keep the logic in that component instead of creating a service.
Practice Questions
What does
providedIn: "root"do? It makes the service an app-wide singleton, created lazily and shared across all components.How do you inject a service into a component? Add it as a constructor parameter with a visibility keyword like
privateorpublic.Why use services for HTTP calls instead of making them in components? Services centralize HTTP logic, enable caching, simplify testing, and allow multiple components to reuse the same data.
How do services share data between components? By holding shared state in a service and exposing it via observables or signals that multiple components can subscribe to.
What happens if you provide a service at the component level? Each component instance gets its own service instance, which is useful for isolated state like form drafts.
Challenge
Build a ShoppingCartService that manages cart items (add, remove, update quantity, clear). Create a CartBadgeComponent that shows the item count and a CartListComponent that shows all items with a total price. Both should stay in sync through the service.
FAQ
Mini Project
Build a TaskManagerService that manages a list of tasks (add, delete, toggle complete, filter by status). Create a TaskInputComponent that adds tasks, a TaskListComponent that displays them with filter buttons (All, Active, Completed), and a TaskCountComponent that shows pending counts. All components should use the service and stay synchronized.
What's Next
Now that you understand services, learn about dependency injection and lifecycle hooks:
Angular DI, Angular Lifecycle, Angular HTTP Interceptors
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro