In Angular development, services, and dependency injection (DI) are one of the core concepts that have importance while building complex, modular, and easy-to-maintain applications. Let's have a look at these key concepts.
In Angular, an entity that can be injected into multiple components, directives, or other services. Services in Angular are used to share common functionalities and manipulate data across different or the same modules of an application.
Angular service class provides a way to organize and share code across different parts of an Angular application. Given below are some key features of Angular services.
Services created in Angular applications are basically singleton instances. Because of this nature, there will be only one instance creation of a service across the application. These services are responsible for data access and state maintenance.
Angular's dependency injection system is mainly used to inject services into components. Because of this facility, functions, and variables can be used by that component class.
Services mainly use encapsulation features such as code reusability for communication purposes. You can use a service inside the same modules as well as multiple modules.
Service contributes to the modularity of the application by allowing you to encapsulate and separate concerns. Each service can handle a specific set of functionalities, making the codebase more organized and maintainable.
Services hide their implementation details from the components that use them. This encapsulation ensures that the internal workings of a service are not exposed, and only a well-defined interface is made available to the rest of the application.
Services in Angular often deal with asynchronous operations, such as handling HTTP requests or managing data streams. They leverage Observables and Promises to handle asynchronous tasks effectively, providing a clean way to manage async operations.
Services are commonly used to encapsulate business logic and data manipulation. This separation of concerns allows for a clear distinction between the presentation layer (components) and the business logic layer (services).
Angular services are designed to be easily testable. Because of the separation of concerns and DI, it is straightforward to write a unit test for services in isolation. Mocking or replacing own dependencies during testing is also simplified.
Services can implement lifecycle hooks, such as `OnInit` or `OnDestroy`, allowing them to perform actions when they are instantiated or destroyed. This can be useful for managing resources or performing operations.
Services can be used to manage global state within an application. Service establishes communication and coordination between different parts of the application by providing a central location for storing and managing shared data.
From Angular CLI, a custom service class can be created by using the following command.
ng generate service <service-name>
It will create two files. Service-name.service.specs.ts is used for unit testing and the other one is Service-name.service.ts which is service file.
Common Examples of Services in Angular Services:
Angular provides the `HttpClient` service to handle HTTP requests. Here data service is created and an instance of HttpClient has been created inside the constructor to interact with APIs and get data from server.
Following code shows how to import and use HttpClient inside service.
// src/app/services/data.service.ts
// Example HTTP service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataService {
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get('https://api.example.com/data');
}
}
An authentication service is used for handling user authentication and authorization. It may include methods for login, update user data, logout, checking user roles, and handling other authenticated actions. Here is an example of an authentication service:
// Example Authentication service
// src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private isAuthenticated = false;
login(): void {
// Perform authentication logic
this.isAuthenticated = true;
}
logout(): void {
// Perform logout logic
this.isAuthenticated = false;
}
isAuthenticatedUser(): boolean {
return this.isAuthenticated;
}
}
A logging service can be used to log messages and errors across the application. Here is an example of logger service:
// src/app/services/logger.service.ts
// Example Logging service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class LoggerService {
log(message: string): void {
console.log(message);
}
error(message: string): void {
console.error(message);
}
}
A service for sharing data between components can use BehaviorSubject or other observable patterns to notify components about changes. BehaviorSubject is generally used when we have initial data to be stored.
// src/app/services/data-sharing.service.ts
// Example Data Sharing service
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataSharingService {
private dataSubject = new BehaviorSubject<string>('Initial Value');
data$ = this.dataSubject.asObservable();
updateData(newValue: string): void {
this.dataSubject.next(newValue);
}
}
Without DI, there will be complications in dealing with dependencies. Components need to be managed internally. Angular's DI system provides easy management features and creates injectable dependencies across the application.
Injector: Angular's injector plays a vital role in creating and managing instances of objects, including services, and injecting them into components as per the needs of applications. It is one of the central components of the injection system.
import { Injector } from '@angular/core';
class MyComponent {
constructor(private injector: Injector) {
const myService = this.injector.get(MyService);
}
}
Providers: Providers in Angular provide information about how to create and configure instances of a service. Each provider is identified as a separate token. The `@Injectable` decorator has the `providedIn` option, which instructs the injector that will be used to create the service.
// src/app/services/my.service.ts
@Injectable({
providedIn: 'root', // or specify a module: { providedIn: SomeModule }
})
export class MyService {
// Service logic goes here
}
Angular components and services declare their dependencies in their respective constructors, and the Angular injector is responsible for injecting the required dependencies.
In Angular, the combination of services and dependency injection contributes to the development of well-organized, modular, and maintainable applications. These concepts are essential for creating robust and scalable Angular applications.
@Injectable Decorator:
Angular services are typically defined using the `@Injectable` decorator. This decorator allows the service to be injected into other components, services, or directives.
Following code block shows how to import and use @Injectable inside service.
// src/app/services/my.service.ts
// Example service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root', // providedIn option registers the service in the root injector
})
export class MyService {
// Service logic goes here
}
To use a service in an Angular component, first, we need to declare the service as a dependency in the component's constructor. Angular's DI system automatically injects the needful dependencies while creating an instance of the component.
Here is an example of injecting service inside a component:
// src/app/components/myComponents/mycomponent.component.ts
// Example component using DI
import { Component } from '@angular/core';
import { MyService } from './my-service.service';
@Component({
selector: 'app-my-component',
template: '<p>{{ message }}</p>',
})
export class MyComponent {
message: string;
constructor(private myService: MyService) {
this.message = myService.getMessage();
}
}
In Angular, services are one of the fundamental building blocks that allows you to create and share code that can be shared across different components/modules of your application.
Services are used to encapsulate and provide specific functionality, such as data manipulation, business logic or retrieval of data with external services. Let's dive deep into the coding of service and communication between components.
We will create an example of an Angular service. In this example, we'll create a service that handles a list of items. The service will have methods to add an item to the list, get the list, and clear the list.
Create a new file for the new service, e.g., item-list.service.ts. or you can use CLI command.
// src/app/services/item-list.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root', // This registers the service in the root injector
})
export class ItemListService {
private items: string[] = [];
addItem(item: string): void {
this.items.push(item);
}
getItems(): string[] {
return this.items;
}
clearItems(): void {
this.items = [];
}
}
Now, let's use this service as a component to demonstrate its functionality.
// src/app/app.component.ts
import { Component } from '@angular/core';
import { ItemListService } from './item.service';
@Component({
selector: 'app-root',
template: `<div>
<h2>Item List</h2>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<button (click)="addItem('New Item')">Add Item</button>
<button (click)="clearItems()">Clear Items</button>
</div>`,
})
export class AppComponent {
items: string[] = [];
constructor(private listService: ItemListService) {
this.items = this.listService.getItems();
}
addItem(item: string): void {
this.listService.addItem(item);
this.items = this.listService.getItems(); // Update the items after adding
}
clearItems(): void {
this.listService.clearItems();
this.items = this.listService.getItems(); // Update the items after clearing
}
}
Make sure to add the service to the providers array in the @NgModule decorator of your app.module.ts (ts file).
// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ItemListService } from './item.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [ItemListService], // Add the ItemListService to the providers array
bootstrap: [AppComponent],
})
export class AppModule {}
Create a new file for the Hero Service, e.g., hero.service.ts.
// hero.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero.model';
@Injectable({
providedIn: 'root',
})
export class HeroService {
private heroes: Hero[] = [
new Hero(1, 'Superman'),
new Hero(2, 'Batman'),
new Hero(3, 'Spiderman'),
new Hero(4, 'Wonder Woman'),
];
getHeroes(): Hero[] {
return this.heroes;
}
getHeroById(id: number): Hero | undefined {
return this.heroes.find(hero => hero.id === id);
}
}
All needed services and components should be added inside providers and declaration array respectively. Here, HeroService is added inside providers array.
You can refer to the code below:
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes.component'; // Import HeroesComponent
import { HeroService } from './hero.service';
@NgModule({
declarations: [AppComponent, HeroesComponent], //HeroesComponent in declarations
imports: [BrowserModule, FormsModule],
providers: [HeroService],
bootstrap: [AppComponent],
})
export class AppModule {}
A constructor is needed to create an instance of a service. Instance of the same service is used across the component to access service functions and variables.
Now, let's use this service in a component to demonstrate its functionality. We are accessing service data from function getHeroes() inside HeroService.
Please refer the code below:
// heroes.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero.model';
import { HeroService } from './hero.service';
@Component({
selector: 'app-heroes',
template: `<div>
<h2>Heroes</h2>
<ul>
<li *ngFor="let hero of heroes">
{{ hero.name }} (ID: {{ hero.id }})
</li>
</ul>
</div>`,
})
export class HeroesComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) {}
ngOnInit(): void {
// Subscribe to the HeroService to get the list of heroes
this.heroService.getHeroes().subscribe((heroes: Hero[]) => {
this.heroes = heroes;
});
}
}
Whenever there is a situation of handling asynchronous data or needing to deal with reactive programming, observable plays a vital role. Observables are a part of RxJs library. Observables are used to handle data streams and manage events over time.
Here's a pictorial representation of how observable works:
Let's have a look at how to work with observables:
Import RxJS and create Observable
First, we need to import RxJS module in component or service wherever we are going to use that observable. You can create observable by using Observable keyword.
import { Observable } from 'rxjs';
const myCreatedObservable = new Observable(observer => {
// Produce values and notify the observer
observer.next('Hello');
observer.next('World');
observer.complete(); // Indicates the end of the observable
});
Subscribe to an Observable:
Now we have created an observable with the name myCreatedObservable. To get the observable value, you must subscribe it. Here's an example of how to subscribe observable info.
myObservable.subscribe(
value => console.log(value), // onNext
error => console.error(error), // onError
() => console.log('Observable completed') // onComplete
);
Angular Services and HTTP Requests:
Observables are mostly used in Angular services when there is a need to deal with asynchronous tasks like HTTP requests. For more clarification, you can refer code below:
import { HttpClient } from '@angular/common/http';
constructor(private http: HttpClient) {}
fetchDataFromAPI(): Observable<any> {
return this.http.get('https://myApi.example.com/myData');
}
Now let's move further in service communication using above observable concepts. Here's a service called hero service. Hero service consists of multiple functions to handle data. Hero service requests heroes via getHeroes() function. Please refer code below:
// hero.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero.model';
@Injectable({
providedIn: 'root',
})
export class HeroService {
private heroes: Hero[] = [
new Hero(1, 'Superman'),
new Hero(2, 'Batman'),
new Hero(3, 'Spiderman'),
new Hero(4, 'Wonder Woman'),
];
getHeroes(): Observable<Hero[]> {
return of(this.heroes);
}
getHeroById(id: number): Observable<Hero | undefined> {
const hero = this.heroes.find((myHero) => myHero.id === id);
return of(hero);
}
}
Create a new file for the MessageComponent, e.g., message.component.ts:
Following command generates components via CLI. ng generate component <component-name>.
// message.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-message',
template: `
<div>
<p>{{ message }}</p>
<button (click)="sendMessage()">Send Message</button>
</div>
`,
})
export class MessageComponent {
@Input() message: string; // Input property to receive the message from the parent component
@Output() messageSent = new EventEmitter<void>(); // Output event to notify the parent component
sendMessage(): void {
this.messageSent.emit(); // Emit the event when the button is clicked
}
}
Create a new file for the MessageService, e.g., message.service.ts:
BehaviourSubject is useful in case of holding latest emitted data. Please refer following diagram to get more clarity about subjects and BehaviourSubject.
You need to create an instance of subject before using it in service. Here, myMessageSubject is an instance of BehaviorSubject with an initial value.
Here message service imports BehaviorSubject from RxJS library.
// message.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class MessageService {
private myMessageSubject = new BehaviorSubject<string>(''); // BehaviorSubject to hold and notify subscribers about the latest message
message$ = this.myMessageSubject.asObservable(); // Observable to subscribe to for receiving messages
sendMessage(message: string): void {
this.myMessageSubject.next(message); // Update the message and notify subscribers
}
}
You can import service inside another service to resolve complex operations. Here all the components can use all services. Also, service B is using service A data before manipulating data as needed.
Please refer following diagrammatic representation:
Here, MessageService is an injectable service. Let's modify the HeroService and inject MessageService to use it to send a message when a hero is added:
// hero.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero.model';
import { MessageService } from './message.service';
@Injectable({
providedIn: 'root',
})
export class HeroService {
private heroes: Hero[] = [
new Hero(1, 'Superman'),
new Hero(2, 'Batman'),
new Hero(3, 'Spiderman'),
new Hero(4, 'Wonder Woman'),
];
constructor(private messageService: MessageService) {}
getHeroes(): Observable<Hero[]> {
return of(this.heroes);
}
getHeroById(id: number): Observable<Hero | undefined> {
const hero = this.heroes.find((h) => h.id === id);
return of(hero);
}
addHero(hero: Hero): void {
this.heroes.push(hero);
this.messageService.sendMessage(`Hero added: ${hero.name}`);
}
}
Service can be created at root level as well as inside module. For data sharing as well as creating various functionalities, service is a useful building block of Angular framework.
If service is created and its service provider present at the root level to be inserted anywhere in the application. Angular Dependency Injection is used to inject into a component. For asynchronous data handling, observable and RxJS Observable libraries can be used.
This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.