Skip to content

Angular Services Explained — Share Data and Logic Across Components

DodaTech Updated 2026-06-28 7 min read

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

  1. Creating multiple instances of a service — If you provide a service in a component's providers array instead of root, each component gets a new instance with separate state.

  2. Not unsubscribing from service observables — Services often expose observables. Components that subscribe should use the async pipe or unsubscribe in ngOnDestroy.

  3. Putting HTTP calls directly in components — Direct HTTP calls in components make testing harder and violate Separation Of Concerns.

  4. Mutating service state directly from components — Components should call service methods to change state, not directly modify service properties.

  5. 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

  1. What does providedIn: "root" do? It makes the service an app-wide singleton, created lazily and shared across all components.

  2. How do you inject a service into a component? Add it as a constructor parameter with a visibility keyword like private or public.

  3. 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.

  4. 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.

  5. 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

What is the difference between service and component?

A component controls a view and handles UI logic. A service encapsulates business logic, data access, and shared state without any UI.

Can a service inject another service?

Yes, use constructor injection like components do. This creates a dependency tree that Angular resolves.

Should services be stateless?

Not necessarily. Services often hold shared state, like a shopping cart or notification list. Use observables to expose state changes.

How do I test a service?

Inject it using TestBed in Angular testing utilities. Mock its dependencies with jasmine spies or mock classes.

What is the `HttpInterceptor` for?

It intercepts HTTP requests and responses for cross-cutting concerns like authentication tokens, logging, and error handling.

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