Angular is a powerful framework for building dynamic web applications. One of the core concepts that makes Angular so flexible and maintainable is the use of services. Services play a crucial role in structuring and organizing business logic, data handling, and application state management.
In this post, we will dive deep into the concept of Angular Services, why they are essential, how to create and use them, and how they interact with Angular’s Dependency Injection (DI) system. By the end, you’ll understand how services help you write modular, reusable, and testable code.
What is an Angular Service?
In Angular, a service is a class with a specific purpose — usually to provide functionality that can be shared across different components. A service could be responsible for retrieving data from an API, managing application state, performing calculations, logging activities, or even handling authentication logic.
The key idea behind services is separation of concerns. Instead of writing all the logic directly inside a component, you can move reusable or complex logic into a service. This keeps your components lean and focused only on the presentation logic.
Example of a Simple Service
Let’s start with a simple service example. Suppose we want to create a service that provides a list of courses.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CourseService {
getCourses() {
return ['Angular', 'React', 'Vue', 'Svelte'];
}
}
Here’s what’s happening:
- The
@Injectable()
decorator tells Angular that this class can be injected as a dependency. - The
providedIn: 'root'
property means this service is registered at the root level, making it a singleton — one instance shared across the entire app. - The
getCourses()
method returns an array of course names.
Now this service can be injected into any component or other service that needs access to this data.
Why Use Services in Angular?
When applications grow in complexity, it’s not practical to keep all logic inside components. Services help solve several key problems:
- Reusability – You can reuse the same service across multiple components without repeating code.
- Maintainability – Changes to shared logic only need to be made in one place.
- Separation of concerns – Components handle view logic, services handle business logic.
- Testability – It’s easier to test services independently from components.
For example, fetching data from an API, logging user activities, or managing authentication tokens should all be handled inside services, not components.
How to Create a Service in Angular
There are two main ways to create a service in Angular:
1. Using Angular CLI
You can use the Angular CLI command to generate a service easily.
ng generate service course
This will create two files:
course.service.ts
– The main service filecourse.service.spec.ts
– The test file for unit testing
The generated service will look like this:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CourseService {
constructor() { }
}
You can now add methods or logic to this service as needed.
2. Manually Creating a Service
You can also manually create a service file.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoggingService {
logMessage(message: string) {
console.log('LOG:', message);
}
}
Then add it to your application and use it wherever required.
Injecting a Service into a Component
After creating a service, you can inject it into a component using Dependency Injection (DI). Angular automatically provides the service instance to any class that declares it in its constructor.
Here’s how to use the CourseService
inside a component.
import { Component, OnInit } from '@angular/core';
import { CourseService } from './course.service';
@Component({
selector: 'app-course-list',
template: `
<h2>Courses</h2>
<ul>
<li *ngFor="let course of courses">{{ course }}</li>
</ul>
`
})
export class CourseListComponent implements OnInit {
courses: string[] = [];
constructor(private courseService: CourseService) {}
ngOnInit() {
this.courses = this.courseService.getCourses();
}
}
In this example:
- The
CourseService
is injected into the component’s constructor. - Inside the
ngOnInit()
lifecycle hook, we callgetCourses()
to fetch data. - The
courses
array is displayed in the template using*ngFor
.
This approach keeps the component clean and free from data-fetching logic.
Understanding the @Injectable Decorator
The @Injectable
decorator is essential for making a class a service. It allows Angular to inject dependencies into the service or inject the service itself into other parts of the app.
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {}
}
- The
providedIn
property determines where the service is registered. - Setting it to
'root'
means it’s globally available as a singleton.
If you omit @Injectable()
, Angular won’t know that this class can participate in dependency injection.
Using Services for API Calls
A common use case for services is to fetch data from external APIs. This keeps components clean and makes the code easier to maintain.
Here’s an example using Angular’s HttpClient
.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private baseUrl = 'https://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) {}
getUsers(): Observable<any> {
return this.http.get(${this.baseUrl}/users
);
}
}
You can now inject this service into any component to get user data.
import { Component, OnInit } from '@angular/core';
import { ApiService } from './api.service';
@Component({
selector: 'app-user-list',
template: `
<h3>Users</h3>
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: any[] = [];
constructor(private apiService: ApiService) {}
ngOnInit() {
this.apiService.getUsers().subscribe(data => {
this.users = data;
});
}
}
This demonstrates how services decouple API logic from components, keeping them easier to test and maintain.
Services and Dependency Injection (DI)
Angular’s Dependency Injection system is the foundation for how services are used. DI allows you to request dependencies (like services) instead of manually creating them.
Instead of writing:
const apiService = new ApiService();
You can simply inject the service into your component, and Angular will automatically handle its creation and lifecycle.
constructor(private apiService: ApiService) {}
This approach ensures that only one instance of the service (if configured as a singleton) is used throughout the application, reducing redundancy and improving efficiency.
Singleton Services
By default, when a service is provided in the root (providedIn: 'root'
), it becomes a singleton. This means there is only one instance of the service for the entire application.
For example, a UserService
that manages the current logged-in user should be a singleton, since all components should share the same user data.
@Injectable({
providedIn: 'root'
})
export class UserService {
private user = { name: 'John Doe', email: '[email protected]' };
getUser() {
return this.user;
}
setUser(newUser: any) {
this.user = newUser;
}
}
This singleton pattern ensures consistent data across all parts of the app.
Providing Services in Modules
You can also provide services at the module level. This is useful when you only want the service to be available within a specific module.
@NgModule({
providers: [FeatureService]
})
export class FeatureModule {}
In this case, FeatureService
is scoped to the FeatureModule
. If multiple modules use the same service independently, each will have its own instance.
Providing Services in Components
You can also provide a service directly at the component level.
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
providers: [DashboardService]
})
export class DashboardComponent {
constructor(private dashboardService: DashboardService) {}
}
This means that every instance of the DashboardComponent
gets its own unique instance of DashboardService
. This approach is useful when you don’t want data shared between components.
Service Hierarchy and Scope
The way services are provided determines their scope and lifetime.
- If provided in the root, they are global singletons.
- If provided in a module, they are shared within that module.
- If provided in a component, each component instance gets a new copy.
This flexibility allows developers to control how state and data flow through the application.
Using Services for State Management
Angular services are a great place to manage shared state across components.
Here’s a simple example of a counter service that multiple components can share.
@Injectable({
providedIn: 'root'
})
export class CounterService {
private count = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
getCount() {
return this.count;
}
}
Any component that injects CounterService
will share the same counter value.
Testing Services
One of the benefits of using services is that they are easy to test independently of components.
Here’s a simple test for CourseService
.
import { TestBed } from '@angular/core/testing';
import { CourseService } from './course.service';
describe('CourseService', () => {
let service: CourseService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CourseService);
});
it('should return course list', () => {
const courses = service.getCourses();
expect(courses.length).toBeGreaterThan(0);
});
});
This approach ensures that your business logic remains correct without needing to test the entire UI.
Best Practices for Using Services
- Keep services focused on a single responsibility.
- Avoid putting UI logic in services — they should handle data and logic only.
- Use Dependency Injection to improve modularity and testability.
- Use singleton services for shared state or caching.
- Name services clearly (e.g.,
UserService
,AuthService
,ApiService
). - Avoid using services as global data stores unless necessary.
Leave a Reply