In Angular, services are used to handle business logic, data access, and shared functionality across multiple components. They provide a way to keep the application logic modular, reusable, and maintainable by separating concerns like data fetching, state management, or complex calculations from the components. Services are typically used in conjunction with dependency injection (DI) to allow for shared functionality across the app.
1. What is a Service?
A service in Angular is a class with a specific purpose, usually to provide data, perform tasks, or share logic between components. Services often interact with external APIs, manage data, or handle non-UI logic. They are designed to be singletons, meaning a single instance of the service is created and shared throughout the application.
2. Creating a Service
You can create a service using the Angular CLI with the following command:
ng generate service my-service
This command generates a new service file (my-service.service.ts) with the basic structure of an Angular service.
Example: A simple logging service
Here’s an example of a basic logging service that logs messages to the console.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class LoggingService {
log(message: string): void {
console.log(message);
}
}
In this example:
- The
@Injectabledecorator indicates that the class can be injected into other components or services. - The
providedIn: 'root'specifies that the service is registered at the root level, meaning it’s a singleton and available throughout the app.
3. Injecting a Service into a Component
Once a service is created, you can inject it into a component (or another service) using Angular’s dependency injection mechanism.
Example: Using the LoggingService in a component
import { Component } from '@angular/core';
import { LoggingService } from './logging.service';
@Component({
selector: 'app-root',
template: `<h1>{{ title }}</h1>`,
})
export class AppComponent {
title = 'Angular Services Example';
constructor(private loggingService: LoggingService) {}
ngOnInit() {
this.loggingService.log('AppComponent initialized');
}
}
Here’s how the LoggingService is injected and used in the AppComponent:
- The service is injected into the component’s constructor (
private loggingService: LoggingService). - In the
ngOnInit()lifecycle hook, the service’slog()method is called to log a message when the component initializes.
4. Registering a Service
There are two ways to register a service in Angular:
a) Provided in Root (Recommended)
The easiest way to register a service is by adding providedIn: 'root' in the @Injectable() decorator. This tells Angular to provide the service at the root level, ensuring a singleton instance throughout the entire application.
@Injectable({
providedIn: 'root',
})
export class MyService {
// Service logic here
}
b) Registering in an NgModule
Alternatively, you can register a service in an NgModule by adding it to the providers array of the module. This method is useful when you want to provide the service at a specific module level rather than the root level.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyService } from './my-service.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [MyService], // Register the service here
bootstrap: [AppComponent],
})
export class AppModule {}
When provided this way, the service is available only to the components that belong to this module.
5. Service with HTTP Operations
Services are commonly used to interact with external APIs for fetching or saving data. Angular provides the HttpClient module, which allows you to make HTTP requests.
Example: A service that fetches data from an API
- First, import the HttpClientModule into your
AppModule:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [HttpClientModule],
// other properties
})
export class AppModule {}
- Then create a service that uses HttpClient to fetch data from an API:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://jsonplaceholder.typicode.com/posts';
constructor(private http: HttpClient) {}
// Fetch data from API
getPosts(): Observable<any> {
return this.http.get<any>(this.apiUrl);
}
}
In this example:
- HttpClient is injected into the
DataServicethrough the constructor. - The
getPosts()method usesHttpClient.get()to send a GET request to the API and returns an Observable of the response.
- Using the service in a component:
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-post-list',
template: `
<div *ngFor="let post of posts">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</div>
`,
})
export class PostListComponent implements OnInit {
posts: any[] = [];
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.getPosts().subscribe((data) => {
this.posts = data;
});
}
}
In this component:
- The
DataServiceis injected into the component, and itsgetPosts()method is called to fetch data when the component initializes. - The retrieved data is stored in the
postsarray and displayed in the template using structural directives like*ngFor.
6. Service with Dependency Injection (DI)
Angular’s dependency injection system is responsible for creating and managing service instances. When you inject a service into a component or another service, Angular ensures that the appropriate instance of the service is provided.
a) Injecting a Service into Another Service
You can inject one service into another, allowing for complex and decoupled logic.
Example: Injecting a logging service into a data service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LoggingService } from './logging.service';
@Injectable({
providedIn: 'root',
})
export class DataService {
private apiUrl = 'https://jsonplaceholder.typicode.com/posts';
constructor(private http: HttpClient, private loggingService: LoggingService) {}
getPosts() {
this.loggingService.log('Fetching posts from API...');
return this.http.get(this.apiUrl);
}
}
In this case, the LoggingService is injected into the DataService and used to log a message before fetching data from the API.
7. Singleton Services and ProvidedIn
Angular services are singleton by default when provided at the root or module level. This means that only one instance of the service is created and shared throughout the application.
a) Multiple Instances of a Service
To create multiple instances of a service, you can provide the service at the component level. In this case, each instance of the component will have its own service instance.
@Component({
selector: 'app-example',
template: `<h1>Component-level Service</h1>`,
providers: [MyService], // Provides a new instance of the service for each component
})
export class ExampleComponent {
constructor(private myService: MyService) {}
}
When provided this way, each component gets a new instance of the MyService, which is useful in scenarios where you need isolated state in different components.
8. Service for Shared Data (State Management)
Services are often used for sharing data between different components. This is especially useful in scenarios where components need to communicate without a direct parent-child relationship.
Example: Shared data service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class SharedDataService {
private data: any;
setData(value: any) {
this.data = value;
}
getData() {
return this.data;
}
}
In this service:
- The
setData()method allows you to store a value. - The
getData()method retrieves the stored value.
This service can then be used to share data between two unrelated components:
- Component A (Sender):
import { Component } from '@angular/core';
import { SharedDataService } from './shared-data.service';
@Component({
selector: 'app-sender',
template: `<button (click)="sendData()">Send Data</button>`,
})
export class SenderComponent {
constructor(private sharedDataService: SharedDataService) {}
sendData() {
this.sharedDataService.setData('Data from SenderComponent');
}
}
- Component B (Receiver):
import { Component, OnInit }
from '@angular/core';
import { SharedDataService } from './shared-data.service';
@Component({
selector: 'app-receiver',
template: `<p>{{ data }}</p>`,
})
export class ReceiverComponent implements OnInit {
data: any;
constructor(private sharedDataService: SharedDataService) {}
ngOnInit() {
this.data = this.sharedDataService.getData();
}
}
In this example:
- The
SenderComponentstores data in the shared service when the button is clicked. - The
ReceiverComponentretrieves the data from the shared service and displays it.
Conclusion
In Angular, services play a crucial role in building maintainable and scalable applications. They allow you to encapsulate business logic, interact with APIs, share data between components, and keep your code modular. Using Angular’s dependency injection, you can manage service lifecycles efficiently, ensuring a consistent experience across the application.
