Angular Services Basics
Introduction
In Angular applications, services are a fundamental building block that help you organize and share code across your application. They are essentially TypeScript classes that can be injected into components and other services, providing a way to keep your component logic clean and focused on the user interface while moving business logic, data fetching, and other shared functionality into reusable service classes.
Services are central to Angular's dependency injection system, which is one of the framework's most powerful features. By the end of this tutorial, you'll understand what services are, why they're important, and how to create and use them in your Angular applications.
What Are Angular Services?
A service in Angular is a TypeScript class with a specific purpose. Services can:
- Share data between components
- Encapsulate business logic
- Connect to external APIs and resources
- Store application state
- Provide utility functions
Unlike components, services don't have templates or views—they focus solely on functionality.
Why Use Services?
Services provide several important benefits:
- Reusability: Write code once and use it across multiple components
- Separation of concerns: Keep components focused on the view while business logic lives in services
- Maintainability: Centralize functionality to make updates easier
- Testability: Services can be tested independently from components
- Shared state: Maintain data that persists across component lifecycles
Creating Your First Service
Let's create a simple service that manages a list of tasks. We'll use the Angular CLI to generate our service:
ng generate service services/Task
This command creates a task.service.ts
file in a services
folder with a basic service skeleton:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TaskService {
constructor() { }
}
The @Injectable()
decorator marks the class as one that participates in the dependency injection system. The providedIn: 'root'
option makes the service available throughout the application as a singleton instance.
Let's add some functionality to our service:
import { Injectable } from '@angular/core';
interface Task {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class TaskService {
private tasks: Task[] = [];
private nextId = 1;
constructor() { }
getTasks(): Task[] {
return this.tasks;
}
addTask(title: string): void {
this.tasks.push({
id: this.nextId++,
title,
completed: false
});
}
deleteTask(id: number): void {
this.tasks = this.tasks.filter(task => task.id !== id);
}
toggleTaskCompletion(id: number): void {
const task = this.tasks.find(task => task.id === id);
if (task) {
task.completed = !task.completed;
}
}
}
Using Services in Components
Now that we have our TaskService
, let's see how to use it in a component:
- First, generate a component:
ng generate component TaskList
- Next, inject the
TaskService
into the component:
import { Component, OnInit } from '@angular/core';
import { TaskService } from '../services/task.service';
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
styleUrls: ['./task-list.component.css']
})
export class TaskListComponent implements OnInit {
taskTitle = '';
constructor(public taskService: TaskService) { }
ngOnInit(): void {
// Initialize component with 2 sample tasks
this.taskService.addTask('Learn Angular Services');
this.taskService.addTask('Build a Todo Application');
}
addNewTask(): void {
if (this.taskTitle.trim()) {
this.taskService.addTask(this.taskTitle);
this.taskTitle = '';
}
}
}
- Create the component template:
<div class="task-container">
<h2>Task List</h2>
<div class="add-task">
<input [(ngModel)]="taskTitle" placeholder="Enter new task..." />
<button (click)="addNewTask()">Add Task</button>
</div>
<ul class="task-list">
<li *ngFor="let task of taskService.getTasks()" class="task-item">
<input
type="checkbox"
[checked]="task.completed"
(change)="taskService.toggleTaskCompletion(task.id)"
/>
<span [class.completed]="task.completed">{{ task.title }}</span>
<button (click)="taskService.deleteTask(task.id)">Delete</button>
</li>
</ul>
</div>
- Add some basic styling:
.task-container {
max-width: 500px;
margin: 0 auto;
}
.add-task {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
.task-list {
list-style-type: none;
padding: 0;
}
.task-item {
padding: 8px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
.completed {
text-decoration: line-through;
color: #888;
}
When the application runs, the TaskListComponent
will inject the TaskService
and use it to manage tasks. The component stays focused on the UI concerns while the service handles data management.
Understanding Dependency Injection
Angular's dependency injection system is what makes services so powerful. Here's how it works:
- You declare a dependency in a component's constructor
- The Angular injector finds or creates the requested service instance
- The service instance is injected into the component
The key benefit is that components don't need to know how services are created—they just use them. This enables looser coupling between classes and makes testing easier.
Service Providers and Scope
When creating a service, you have different options for how it's provided:
Root-level providers
@Injectable({
providedIn: 'root'
})
This creates a singleton instance available throughout your application. Most services should use this approach.
Module-level providers
@NgModule({
providers: [TaskService]
})
This creates a singleton instance within a specific NgModule.
Component-level providers
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
providers: [TaskService]
})
This creates a new instance for each instance of the component and its child components.
Real-World Example: Data Service with HTTP
Let's create a more practical example—a service that fetches data from an API:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
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);
}
updateUser(id: number, changes: Partial<User>): Observable<User> {
return this.http.patch<User>(`${this.apiUrl}/${id}`, changes);
}
deleteUser(id: number): Observable<unknown> {
return this.http.delete(`${this.apiUrl}/${id}`);
}
}
And here's how you might use this service in a component:
import { Component, OnInit } from '@angular/core';
import { UserService } from '../services/user.service';
@Component({
selector: 'app-user-list',
template: `
<div *ngIf="loading">Loading users...</div>
<div *ngIf="error">{{ error }}</div>
<ul *ngIf="users.length">
<li *ngFor="let user of users">
{{ user.name }} ({{ user.email }})
</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: any[] = [];
loading = false;
error = '';
constructor(private userService: UserService) { }
ngOnInit(): void {
this.loading = true;
this.userService.getUsers().subscribe({
next: (data) => {
this.users = data;
this.loading = false;
},
error: (err) => {
this.error = 'Failed to load users: ' + err.message;
this.loading = false;
}
});
}
}
This example demonstrates how a service can encapsulate all API interaction logic, keeping the component clean and focused on displaying the data.
Service Communication
Services can also be used to facilitate communication between unrelated components:
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class MessageService {
// Subject acts as both an Observable and an Observer
private messageSource = new Subject<string>();
// Expose the Subject as an Observable for subscribers
message$ = this.messageSource.asObservable();
// Method to send messages
sendMessage(message: string): void {
this.messageSource.next(message);
}
}
Then any component can send messages:
import { Component } from '@angular/core';
import { MessageService } from '../services/message.service';
@Component({
selector: 'app-sender',
template: `
<input #messageInput placeholder="Type message" />
<button (click)="sendMessage(messageInput.value)">Send</button>
`
})
export class SenderComponent {
constructor(private messageService: MessageService) { }
sendMessage(message: string): void {
this.messageService.sendMessage(message);
}
}
And any other component can receive those messages:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MessageService } from '../services/message.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-receiver',
template: `
<div>Last message received: {{ lastMessage || 'None' }}</div>
`
})
export class ReceiverComponent implements OnInit, OnDestroy {
lastMessage = '';
private subscription: Subscription | undefined;
constructor(private messageService: MessageService) { }
ngOnInit(): void {
this.subscription = this.messageService.message$.subscribe(
message => this.lastMessage = message
);
}
ngOnDestroy(): void {
// Always unsubscribe to prevent memory leaks
this.subscription?.unsubscribe();
}
}
This pattern allows completely unrelated components to communicate without direct references to each other.
Summary
Angular services are a powerful way to organize your application code:
- Services are singleton objects that provide functionality across your application
- Dependency Injection makes services easy to use and test
- Services help maintain separation of concerns by keeping business logic out of components
- Services are perfect for data fetching, state management, and cross-component communication
By leveraging services effectively, you'll write more maintainable, testable, and scalable Angular applications.
Additional Resources
- Official Angular Services Guide
- Angular Dependency Injection Documentation
- RxJS Library (Used extensively with Angular services)
Exercises
-
Create a
CounterService
that keeps track of a count value and provides methods to increment, decrement, and reset the counter. -
Build a
LocalStorageService
that wraps browser's localStorage API and provides methods to get, set, and remove items with proper typing. -
Create a
ThemeService
that allows components to switch between light and dark themes, broadcasting theme changes to all subscribed components. -
Extend the TaskService to persist tasks to localStorage so they survive page refreshes.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)