How to Use Angular Services

How to Use Angular Services Angular services are one of the most fundamental and powerful concepts in the Angular framework. They provide a structured, reusable, and testable way to encapsulate and share logic across components. Whether you’re fetching data from an API, managing application state, validating user input, or logging events, services are the backbone of scalable Angular applications.

Nov 10, 2025 - 08:47
Nov 10, 2025 - 08:47
 2

How to Use Angular Services

Angular services are one of the most fundamental and powerful concepts in the Angular framework. They provide a structured, reusable, and testable way to encapsulate and share logic across components. Whether youre fetching data from an API, managing application state, validating user input, or logging events, services are the backbone of scalable Angular applications. Understanding how to create, inject, and use services effectively is essential for any developer working with Angular from beginners to seasoned professionals.

In this comprehensive guide, well walk you through everything you need to know about Angular services. Youll learn how to build them from scratch, inject them into components, manage their lifecycle, follow best practices, leverage real-world examples, and avoid common pitfalls. By the end of this tutorial, youll have the confidence and knowledge to use Angular services efficiently in any project.

Step-by-Step Guide

What Is an Angular Service?

An Angular service is a class designed to handle specific tasks that are not directly related to the UI. Unlike components, which are responsible for rendering views and handling user interactions, services are focused on business logic, data management, and utility functions. Services are typically used to:

  • Fetch data from remote APIs
  • Store and manage application state
  • Validate forms or user input
  • Handle authentication and authorization
  • Log events or errors
  • Manage browser storage (localStorage, sessionStorage)

Services are instantiated once per injector and can be shared across multiple components, making them ideal for maintaining consistent state and reducing code duplication.

Creating a Service

To create a service in Angular, you can use the Angular CLI, which automates the generation process. Open your terminal and navigate to your Angular project directory. Then run:

ng generate service services/data

This command creates two files:

  • data.service.ts the service class definition
  • data.service.spec.ts a unit test file for the service

The generated service looks like this:

import { Injectable } from '@angular/core';

@Injectable({

providedIn: 'root'

})

export class DataService {

constructor() { }

}

The @Injectable() decorator is required for Angular to recognize the class as a service. The providedIn: 'root' option tells Angular to provide the service at the root injector level, making it a singleton available throughout the entire application. This is the recommended approach in modern Angular applications (Angular 6+).

Adding Methods to a Service

Lets enhance our DataService to fetch user data from a mock API. Well use Angulars HttpClient module to make HTTP requests.

First, import HttpClientModule in your app.module.ts:

import { NgModule } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({

declarations: [

AppComponent

],

imports: [

BrowserModule,

HttpClientModule // Add this line

],

providers: [],

bootstrap: [AppComponent]

})

export class AppModule { }

Now update your service to include an HTTP method:

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 DataService {

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});

}

}

Here, weve defined an interface User to type our data, and two methods: getUsers() and getUserById(). The HttpClient is injected via the constructor, and we use it to make HTTP GET requests. The methods return Observable<User[]> and Observable<User> respectively, allowing components to subscribe to the data as it arrives.

Injecting a Service into a Component

Now that our service is ready, we can use it in a component. Lets create a UserListComponent:

ng generate component user-list

In user-list.component.ts:

import { Component, OnInit } from '@angular/core';

import { DataService, User } from '../services/data.service';

@Component({

selector: 'app-user-list',

templateUrl: './user-list.component.html',

styleUrls: ['./user-list.component.css']

})

export class UserListComponent implements OnInit {

users: User[] = [];

loading = false;

error: string | null = null;

constructor(private dataService: DataService) { }

ngOnInit(): void {

this.loadUsers();

}

loadUsers(): void {

this.loading = true;

this.error = null;

this.dataService.getUsers().subscribe({

next: (users) => {

this.users = users;

this.loading = false;

},

error: (err) => {

this.error = 'Failed to load users. Please try again.';

this.loading = false;

}

});

}

}

Notice how we inject the DataService into the components constructor. Angulars dependency injection system automatically provides the singleton instance of the service. We then call getUsers() in ngOnInit() and subscribe to the observable to handle the response.

In the template (user-list.component.html):

<div class="user-list">

<h2>Users</h2>

<div *ngIf="loading">Loading users...</div>

<div *ngIf="error">{{ error }}</div>

<ul *ngIf="!loading && !error">

<li *ngFor="let user of users">

<strong>{{ user.name }}</strong> {{ user.email }}

</li>

</ul>

</div>

This template displays a loading state, an error message, or the list of users based on the components state. The *ngFor directive iterates over the users array, rendering each users name and email.

Using Services for State Management

Services can also be used to manage shared state across components. For example, lets create a CartService to manage items in a shopping cart.

import { Injectable } from '@angular/core';

export interface CartItem {

id: number;

name: string;

price: number;

quantity: number;

}

@Injectable({

providedIn: 'root'

})

export class CartService {

private cart: CartItem[] = [];

addToCart(item: CartItem): void {

const existingItem = this.cart.find(i => i.id === item.id);

if (existingItem) {

existingItem.quantity += item.quantity;

} else {

this.cart.push({ ...item });

}

}

getCart(): CartItem[] {

return [...this.cart]; // Return a copy to prevent external mutation

}

removeFromCart(id: number): void {

this.cart = this.cart.filter(item => item.id !== id);

}

getTotalItems(): number {

return this.cart.reduce((sum, item) => sum + item.quantity, 0);

}

getTotalPrice(): number {

return this.cart.reduce((sum, item) => sum + item.price * item.quantity, 0);

}

clearCart(): void {

this.cart = [];

}

}

This service maintains a private array of cart items and provides methods to add, remove, and calculate totals. Because the service is a singleton, any component that injects it will share the same cart state.

Now, in a ProductComponent, you can add items to the cart:

import { Component } from '@angular/core';

import { CartService, CartItem } from '../services/cart.service';

@Component({

selector: 'app-product',

template:

<div>

<h3>{{ product.name }} ${{ product.price }}</h3>

<button (click)="addToCart()">Add to Cart</button>

<p>Cart items: {{ cartService.getTotalItems() }}</p>

</div>

})

export class ProductComponent {

product = { id: 1, name: 'Laptop', price: 999 };

constructor(public cartService: CartService) { }

addToCart(): void {

this.cartService.addToCart({ ...this.product, quantity: 1 });

}

}

And in a separate CartComponent, you can display the cart contents:

import { Component } from '@angular/core';

import { CartService, CartItem } from '../services/cart.service';

@Component({

selector: 'app-cart',

template:

<h2>Your Cart</h2>

<ul>

<li *ngFor="let item of cartService.getCart()">

{{ item.name }} x{{ item.quantity }} ${{ item.price * item.quantity }}

</li>

</ul>

<p>Total: ${{ cartService.getTotalPrice() }}</p>

<button (click)="cartService.clearCart()">Clear Cart</button>

})

export class CartComponent {

constructor(public cartService: CartService) { }

}

Now, when a user adds an item in ProductComponent, it appears instantly in CartComponent because both components share the same service instance.

Using Services with RxJS for Advanced Data Flow

For more sophisticated state management, combine services with RxJS subjects. For example, lets create a NotificationService that emits messages to any component that subscribes to them.

import { Injectable } from '@angular/core';

import { BehaviorSubject } from 'rxjs';

export interface Notification {

message: string;

type: 'success' | 'error' | 'warning';

}

@Injectable({

providedIn: 'root'

})

export class NotificationService {

private notificationSubject = new BehaviorSubject<Notification | null>(null);

notification$ = this.notificationSubject.asObservable();

showNotification(message: string, type: 'success' | 'error' | 'warning'): void {

this.notificationSubject.next({ message, type });

}

clearNotification(): void {

this.notificationSubject.next(null);

}

}

Now, any component can subscribe to notifications:

import { Component, OnInit, OnDestroy } from '@angular/core';

import { Subscription } from 'rxjs';

import { NotificationService, Notification } from '../services/notification.service';

@Component({

selector: 'app-header',

template:

<header>

<h1>My App</h1>

<div *ngIf="currentNotification" [class]="currentNotification.type">

{{ currentNotification.message }}

</div>

</header>

,

styles: [ .success { background-color:

d4edda; color: #155724; padding: 8px; }

.error { background-color:

f8d7da; color: #721c24; padding: 8px; }

.warning { background-color:

fff3cd; color: #856404; padding: 8px; }

]

})

export class HeaderComponent implements OnInit, OnDestroy {

currentNotification: Notification | null = null;

private subscription: Subscription = new Subscription();

constructor(private notificationService: NotificationService) { }

ngOnInit(): void {

this.subscription.add(

this.notificationService.notification$.subscribe(notification => {

this.currentNotification = notification;

})

);

}

ngOnDestroy(): void {

this.subscription.unsubscribe();

}

}

This pattern allows you to decouple notification producers from consumers. For instance, a login component can call showNotification('Login successful!', 'success'), and the header will display it automatically no direct component-to-component communication required.

Best Practices

Use providedIn: 'root' for Singleton Services

Always use providedIn: 'root' unless you have a specific reason to limit the services scope. This ensures the service is instantiated only once, improving performance and memory usage. Avoid manually registering services in module providers unless youre using lazy loading or need a different injector context.

Keep Services Focused and Single-Purpose

Follow the Single Responsibility Principle. A service should handle one type of concern for example, one service for HTTP calls, another for authentication, and another for local storage. Avoid creating god services that do everything. This improves testability, maintainability, and reusability.

Use Interfaces to Type Your Data

Always define TypeScript interfaces for the data your services return. This provides compile-time safety, better IDE autocomplete, and clearer code intent. For example:

export interface Product {

id: number;

name: string;

price: number;

category: string;

}

Use these interfaces in your service methods and component properties to ensure consistency.

Handle Errors Gracefully

HTTP requests can fail due to network issues, server errors, or invalid responses. Always handle errors in your service methods or in the components subscription. Avoid letting unhandled errors crash your application.

Consider creating a centralized error handler service:

import { Injectable } from '@angular/core';

import { HttpErrorResponse } from '@angular/common/http';

@Injectable({

providedIn: 'root'

})

export class ErrorHandlerService {

handleHttpError(error: HttpErrorResponse): string {

if (error.status === 0) {

return 'Network error. Please check your connection.';

} else if (error.status === 404) {

return 'Resource not found.';

} else if (error.status === 500) {

return 'Server error. Please try again later.';

} else {

return error.error?.message || 'An unknown error occurred.';

}

}

}

Inject this service into your data services to standardize error handling.

Use RxJS Operators for Data Transformation

Instead of manipulating data in components, use RxJS operators like map, filter, switchMap, and catchError inside your services to transform and handle streams before they reach components.

import { map, catchError } from 'rxjs/operators';

getUsers(): Observable<User[]> {

return this.http.get<User[]>(this.apiUrl)

.pipe(

map(users => users.map(user => ({

...user,

displayName: user.name.toUpperCase()

}))),

catchError(error => {

console.error('Failed to fetch users', error);

return []; // Return empty array on error

})

);

}

This keeps your components clean and focused on presentation, not data manipulation.

Use Async Pipe for Automatic Subscription Management

In templates, use Angulars async pipe to automatically subscribe and unsubscribe from observables. This prevents memory leaks and reduces boilerplate code.

Instead of manually subscribing in the component:

// Avoid this

this.dataService.getUsers().subscribe(users => this.users = users);

Use this in the template:

<div *ngFor="let user of (users$ | async)">

{{ user.name }}

</div>

And in the component:

users$ = this.dataService.getUsers();

This approach is cleaner and automatically handles unsubscribing when the component is destroyed.

Test Your Services

Services are ideal candidates for unit testing because theyre decoupled from the UI. Use Jasmine and Angulars TestBed to test service logic independently.

import { TestBed } from '@angular/core/testing';

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { DataService } from './data.service';

describe('DataService', () => {

let service: DataService;

let httpMock: HttpTestingController;

beforeEach(() => {

TestBed.configureTestingModule({

imports: [HttpClientTestingModule],

providers: [DataService]

});

service = TestBed.inject(DataService);

httpMock = TestBed.inject(HttpTestingController);

});

afterEach(() => {

httpMock.verify();

});

it('should fetch users', () => {

const mockUsers = [

{ id: 1, name: 'John', email: 'john@example.com' }

];

service.getUsers().subscribe(users => {

expect(users).toEqual(mockUsers);

});

const req = httpMock.expectOne('https://jsonplaceholder.typicode.com/users');

expect(req.request.method).toBe('GET');

req.flush(mockUsers);

});

});

Testing services ensures your business logic remains reliable during refactoring and updates.

Tools and Resources

Angular CLI

The Angular CLI is your primary tool for generating services, components, and modules. Use commands like:

  • ng generate service services/name creates a new service
  • ng generate component name creates a component with integrated service injection
  • ng test runs unit tests for your services

Angular DevTools

Install the Angular DevTools browser extension (available for Chrome and Firefox). It allows you to inspect your applications dependency injection tree, view service instances, and track component-state changes in real time.

RxJS Documentation

Since services often work with observables, mastering RxJS is critical. Visit rxjs.dev for comprehensive guides, marble diagrams, and operator references.

JSONPlaceholder

Use JSONPlaceholder as a free, fake REST API for testing your HTTP services without needing a backend server.

Angular Style Guide

Follow the official Angular Style Guide for naming conventions, folder structure, and service organization. It recommends placing services in a services/ folder, using .service.ts suffixes, and keeping related services and components in feature modules.

VS Code Extensions

Enhance your development workflow with these extensions:

  • Angular Language Service provides autocomplete and error checking for templates
  • Angular Snippets quick code templates for services, components, and directives
  • Path Intellisense auto-completes import paths

Stack Overflow and Angular Discord

When you encounter issues, search Stack Overflow or join the Angular Discord community. Many experienced developers are active and willing to help with service injection problems, RxJS operators, or dependency injection quirks.

Real Examples

Example 1: Authentication Service

Heres a realistic authentication service that manages user login state and token storage:

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { BehaviorSubject, Observable } from 'rxjs';

import { map } from 'rxjs/operators';

export interface User {

id: number;

username: string;

token: string;

}

@Injectable({

providedIn: 'root'

})

export class AuthService {

private userSubject = new BehaviorSubject<User | null>(null);

public user$ = this.userSubject.asObservable();

private apiUrl = 'https://api.example.com/auth';

constructor(private http: HttpClient) {

// Load user from localStorage on app start

const savedUser = localStorage.getItem('user');

if (savedUser) {

this.userSubject.next(JSON.parse(savedUser));

}

}

login(username: string, password: string): Observable<User> {

return this.http.post<User>(${this.apiUrl}/login, { username, password })

.pipe(

map(user => {

localStorage.setItem('user', JSON.stringify(user));

this.userSubject.next(user);

return user;

})

);

}

logout(): void {

localStorage.removeItem('user');

this.userSubject.next(null);

}

isLoggedIn(): boolean {

return !!this.userSubject.value;

}

getCurrentUser(): User | null {

return this.userSubject.value;

}

}

This service:

  • Stores the user in localStorage to persist login across page reloads
  • Uses a BehaviorSubject to notify components of login/logout events
  • Provides helper methods like isLoggedIn() and getCurrentUser()

Components can then react to authentication changes:

<div *ngIf="authService.isLoggedIn()">

Welcome, {{ authService.getCurrentUser()?.username }}!

<button (click)="authService.logout()">Logout</button>

</div>

<div *ngIf="!authService.isLoggedIn()">

<app-login></app-login>

</div>

Example 2: Configuration Service

Many applications need to load configuration data dynamically such as API endpoints, feature flags, or theme settings. A configuration service can load this data at startup:

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { BehaviorSubject } from 'rxjs';

export interface AppConfig {

apiUrl: string;

enableAnalytics: boolean;

theme: 'light' | 'dark';

}

@Injectable({

providedIn: 'root'

})

export class ConfigService {

private configSubject = new BehaviorSubject<AppConfig | null>(null);

public config$ = this.configSubject.asObservable();

constructor(private http: HttpClient) { }

loadConfig(): Observable<AppConfig> {

return this.http.get<AppConfig>('/assets/config.json')

.pipe(

map(config => {

this.configSubject.next(config);

return config;

})

);

}

getConfig(): AppConfig | null {

return this.configSubject.value;

}

isFeatureEnabled(feature: string): boolean {

const config = this.configSubject.value;

return config ? config[feature as keyof AppConfig] as boolean : false;

}

}

Load the config in your AppModule using a APP_INITIALIZER:

import { NgModule, APP_INITIALIZER } from '@angular/core';

import { ConfigService } from './services/config.service';

export function initConfig(configService: ConfigService) {

return () => configService.loadConfig().toPromise();

}

@NgModule({

providers: [

ConfigService,

{

provide: APP_INITIALIZER,

useFactory: initConfig,

deps: [ConfigService],

multi: true

}

]

})

export class AppModule { }

This ensures configuration is loaded before the app renders, preventing race conditions.

Example 3: Local Storage Service

Instead of calling localStorage.setItem() directly in components, encapsulate it in a service for easier testing and abstraction:

import { Injectable } from '@angular/core';

@Injectable({

providedIn: 'root'

})

export class StorageService {

setItem(key: string, value: any): void {

localStorage.setItem(key, JSON.stringify(value));

}

getItem(key: string): T | null {

const item = localStorage.getItem(key);

return item ? JSON.parse(item) : null;

}

removeItem(key: string): void {

localStorage.removeItem(key);

}

clear(): void {

localStorage.clear();

}

}

Now components use it safely:

constructor(private storage: StorageService) {}

savePreferences(preferences: any): void {

this.storage.setItem('preferences', preferences);

}

loadPreferences(): any {

return this.storage.getItem('preferences');

}

FAQs

What is the difference between a service and a component in Angular?

Components are responsible for rendering views and handling user interactions. They have templates, styles, and lifecycle hooks. Services, on the other hand, are plain TypeScript classes that handle business logic, data fetching, or utility functions. They do not have templates or styles and are designed to be injected into components or other services.

Can a service inject another service?

Yes. Services can inject other services through their constructors. For example, an AuthService might inject a StorageService to save tokens, and a NotificationService to display login feedback. This promotes modularity and reusability.

Do I need to unsubscribe from service observables?

If you subscribe manually in a component using .subscribe(), you must unsubscribe to prevent memory leaks. However, if you use the async pipe in templates, Angular handles unsubscribing automatically. Always prefer the async pipe when possible.

What happens if I dont use providedIn: 'root'?

If you omit providedIn or set it to null, you must manually register the service in a modules providers array. This can lead to multiple instances if the service is provided in multiple lazy-loaded modules. Using providedIn: 'root' ensures a single global instance, which is almost always what you want.

Can services be used in non-Angular applications?

No. Angular services are tightly coupled with Angulars dependency injection system and decorators like @Injectable(). They cannot be used directly in vanilla JavaScript or React applications. However, the patterns they represent such as separation of concerns and dependency injection are universal and can be implemented in other frameworks.

How do I test a service that uses HTTP?

Use Angulars HttpClientTestingModule and HttpTestingController to mock HTTP requests. You can verify that the correct URL was called, the request method was correct, and the service responded appropriately to success or error responses.

Why use BehaviorSubject instead of Subject?

A BehaviorSubject holds the latest value and emits it immediately to new subscribers. A regular Subject only emits values to subscribers who are already listening. For state management (like user authentication or app config), BehaviorSubject is preferred because new components should receive the current state upon subscription.

Conclusion

Angular services are not just a feature they are a foundational pillar of scalable, maintainable, and testable applications. By encapsulating logic in services, you create reusable, decoupled components that are easier to debug, extend, and test. Whether youre fetching data from an API, managing application state, or handling user preferences, services provide the structure needed to build enterprise-grade applications.

In this guide, youve learned how to create, inject, and use services effectively. Youve seen real-world examples for authentication, configuration, and state management. Youve explored best practices like using providedIn: 'root', typing your data, handling errors, and leveraging RxJS. You now have the tools to write clean, efficient, and professional Angular code.

Remember: the power of Angular lies not in its components, but in how those components communicate through well-designed services. Master services, and you master Angular.