A Detailed Guide on Using Angular JS Services for Cross-Component Communication

    Monday, March 18, 202414 min read189 views
    A Detailed Guide on Using Angular JS Services for Cross-Component Communication

    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.

    Angular Service Class

    Angular Service Class

    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.

    Features of Angular Services

    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.

    1. Singleton Pattern

    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.

    2. Dependency Injection

    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.

    3. Reusability

    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.

    4. Modularity

    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.

    5. Encapsulation

    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.

    6. Async Operations

    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.

    7. Business Logic

    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).

    8. Testing

    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.

    9. Lifecycle Hooks

    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.

    10. Global State Management

    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.

    Angular Service Example

    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:

    1. HTTP Service

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

    2. Authentication Service

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

    3. Logging Service

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

    4. Data Sharing Service

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

    Dependency Injection (DI) in Angular Services

    Dependency Injection (DI) in Angular Services

    1. Definition

    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.

    2. Key Concepts

    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
    }

    3. Implementation

    Angular components and services declare their dependencies in their respective constructors, and the Angular injector is responsible for injecting the required dependencies.

    4. Advantages

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

    Introduction to Services

    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 = [];
    }
    }

    Using a Service for Data Sharing

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

    How to Register a Service in Angular App

    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 Hero Service

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

    Get Hero Data

    Provide the HeroService

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

    Subscribe in HeroesComponent

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

    Observable data

    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 Message Component

    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 the MessageService

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

    Send a message from HeroService

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

    Conclusion

    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.

    24

    Related articles

    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.