Services – Angular

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 @Injectable decorator 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’s log() 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

  1. First, import the HttpClientModule into your AppModule:
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [HttpClientModule],
  // other properties
})
export class AppModule {}
  1. 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 DataService through the constructor.
  • The getPosts() method uses HttpClient.get() to send a GET request to the API and returns an Observable of the response.
  1. 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 DataService is injected into the component, and its getPosts() method is called to fetch data when the component initializes.
  • The retrieved data is stored in the posts array 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:

  1. 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');
  }
}
  1. 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 SenderComponent stores data in the shared service when the button is clicked.
  • The ReceiverComponent retrieves 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.

Tags: No tags

Add a Comment

Your email address will not be published. Required fields are marked *