Using Angular Services for Component Communication using Event Subscription and Notifications

Posted by: Mahesh Sabnis , on 6/17/2018, in Category AngularJS
Views: 6391
Abstract: Use Angular services to communicate data across components using Event Subscription and Notifications.

Components are the major building blocks of an Angular application. When building a complex application like a dashboard, we may have more than one components bootstrapped to the UI. In this case, a common requirement is to send data from one component to another component without having any direct relationship between them.

Angular provides @Input and @Output decorators where the parent component can send data to the child component using the property decorated with @Input decorator inside the child component. However in this approach, the HTML template of the child component must be rendered through the HTML template of the parent component. I have already written an article about this approach over here www.dotnetcurry.com/angularjs/1323/angular2-passing-data-master-detail-component.

In this article, we will discuss another approach to implement communication across components without using @Input and @Output decorators. We will be implementing this approach using Angular Services.

Angular services contain logic that is independent of the components. These services can be used in multiple components.

Generally, these services are used to make an external call using HTTP to REST APIs or they can also be used to perform Socket communication or for that matter, any logic that is independent of the view of the components. In our case, we will use services which will act as a bridge between two components so that one component can send the data and the second component can accept it using services and processes it.

The following figure explains the implementation:

angular-component-communication

Figure 1: The Approach of using Angular Service to establish communication across components

­As shown in the above diagram, we have a sender component, receiver component, and the Angular service. This service will define parameters, an EventEmitter object and a method. This object will be used by receiver component to subscribe to an event. The method defined in the service will accept the parameter, this method will be called by the sender component. This method will then emit the event by passing the received parameter to it.

Since the receiver component has already subscribed to the EventEmitter object, the service will notify the received parameter from the sender component to the receiver component.

Angular Services Communication - Implementation

To implement the application, I have used Visual Studio Code (VSCode) and WebPack Module-Loader. You can also use Angular CLI.

The source-code of the article can be downloaded to understand configurations in package.json and the webpack.config.js file.

Step 1: Create a folder on the drive and open it in the VS Code. This folder will be workspace for the project. The following image shows the folder structure:

initial-folder-structure

Figure 2: The project folder structure

As shown in the above image, the config folder contains utility.js. This file contains node.js module for resolving file paths. This module is used in the webpack.config.js to resolve file paths for the application. The deps folder contains polyfills.ts and stdpkgs.ts. These files contain code for zone.js configuration and imports for all standard Angular packages respectively. The app folder contains all the application code files. The webpack.config.js file contains code for reading all source files and files from the deps folder. The module loaders are managed by this file.

Step 2: In the app folder add following typescript files

  • app.category.model.ts
  • app.product.model.ts
  • app.categorysender.component.ts
  • app.productreceiver.component.ts
  • app.communication.service.ts

Add the following code in the app.category.model.ts

export class Category {
    constructor(
        public CatId: number,
        public CatName: string
    ) { }
}

export const Categories: Array<Category> = new Array<Category>();
Categories.push(new Category(101, "Electronics"));
Categories.push(new Category(102, "Electrical"));
Categories.push(new Category(103, "Mechanical"));

The above code defines Category class with required properties and Categories array with default data.

Add the following code in app.product.model.ts

export class Product {
    constructor(
        public ProdId: number,
        public ProdName: string,
        public CatId: number
    ) { }
}

export const Products: Array<Product> = new Array<Product>();
Products.push(new Product(10001, "Laptop", 101));
Products.push(new Product(10002, "Router", 101));
Products.push(new Product(10003, "Iron", 102));
Products.push(new Product(10004, "Mixer", 102));
Products.push(new Product(10005, "Cranc", 103));
Products.push(new Product(10006, "Leath Machine", 103)); 

The above code defines Product class with required properties and Products array with default data.

Add the following code in app.communication.service.ts

import { Injectable, EventEmitter,  } from "@angular/core";
@Injectable()
export class CommunicationService {
    // 1
    id: number;
    
    
    receivedFilter: EventEmitter<number>;
    constructor() {
        this.receivedFilter = new EventEmitter<number>();
    }
    // 2
    raiseEvent(id: number): void {
        this.id = id;
        this.receivedFilter.emit(id);
    }
}

The above code has following specifications (Note: following numbers matches with the comments applied on the code.)

1. The id member variable is declared in the service class. The value for this member will be set by the sender component. The receivedFilter member of the type EventEmitter is declared. This event can be subscribed by some other object. In our case, the subscriber will be receiver component. This will emit the value received for id member to the subscriber i.e. the receiver component.

2. The raiseEvent() method will accept a parameter. This method will be called by the sender component. The sender component will pass value to this method. The method will accept parameter and will emit the receivedFilter event by passing parameter to it.

Add the following code in app.categorysender.component.ts

import { Component, OnInit } from "@angular/core";
import { Category, Categories } from "./app.category.model";
import { CommunicationService } from "./app.communication.service";
@Component({
    selector: "app-catsender-component",
    template: `
       <div class="container">
         <h2><strong>Category List</strong> </h2>

       <table class="table table-bordered table-striped">
          <thead>
              <tr>
                <td>Category Id</td>
                <td>Category Name</td>
              </tr>
          </thead>
          <tbody>
            <tr *ngFor="let c of cats" (click)="getSelectedCategory(c)">
               <td>{{c.CatId}}</td>
               <td>{{c.CatName}}</td>
            </tr>
          </tbody>
       </table>
       </div>
    `
})

export class CategorySenderComponent implements OnInit {
    cat: Category;
    cats = Categories;

    constructor(private serv: CommunicationService) {
        this.cat = new Category(0, "");
    }


    ngOnInit(): void { }

    
    getSelectedCategory(c: Category): void {
        this.cat = c;
        this.serv.raiseEvent(this.cat.CatId);
    }
}

The above code defines CategorySenderComponent which has the most important getSelectedCategory() method.

The getSelectedCategory() method makes a call to the raiseEvent() method of the service class. The getSelectedCategory() method is bound with the table row of HTML table using click event binding. When the row is clicked, the Category object is passed to the getSelectedCategory() method. This method reads CatId from the Category object and passes it to the raiseEvent() method.

Add the following code in app.productreceiver.component.ts defines ProductReceiverComponent

// 1.
import { Component, OnInit } from "@angular/core";
import { Product, Products } from "./app.product.models";
import { CommunicationService } from "./app.communication.service";
// 2.
@Component({
    selector: "app-prdreceiver-component",
    template: `
   <div class="container">
     <h2>
     <strong>
     Products List for Category:{{CatId}}
     </strong>
     </h2>

  <table class="table table-bordered table-striped">
          <thead>
              <tr>
              <td>Product Id</td>
                <td>Product Name</td>
                <td>Category Id</td>
              </tr>
          </thead>
          <tbody>
            <tr *ngFor="let p of FilterProducts">
            <td>{{p.ProdId}}</td>
            <td>{{p.ProdName}}</td>
            <td>{{p.CatId}}</td>
            </tr>
          </tbody>
       </table>
       </div>
    `
})
export class ProductReceiverComponent implements OnInit {
    // 3.
    prd: Product;
    prds = Products;
    CatId: number;
    private _FilterProducts: Array<Product>;
    constructor(private serv: CommunicationService) {
        this.prd = new Product(0, "", 0);
        this.CatId = 0;
        this._FilterProducts = new Array<Product>();
    }

    // 4.
    ngOnInit() {
        this.serv.receivedFilter.subscribe((param: number) => {
            this.CatId = param;
        });
    }

    // 5.
    get FilterProducts(): Array<Product> {
        this._FilterProducts = new Array<Product>();
        if (this.CatId > 0) {
            this.prds.forEach(p => {
                if (p.CatId === this.CatId) {
                    this._FilterProducts.push(p);
                }
            });
        } else {
            this._FilterProducts = this.prds;
        }
        return this._FilterProducts;
    }
}

1. The above code has the following important specifications. The ngOnInit() method subscribes to the receivedFilter event from the service and receives the parameter from it. The value from the parameter is set to the CatId declared in the component.

2. The FilterProducts() is a read-only property. This property will be invoked when the component is updated based on the change in the value of the CatId member. This read-only property will then iterate over the prds array and read all products based on the CatId. The FilterProducts() is bound with the HTML table to generate rows dynamically. If no match is found, it will return all products.

Angular Bootstrapping

Step 3: Add the main.ts file in the app folder and add the following code in it:

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { FormsModule } from "@angular/forms";
import { CategorySenderComponent } from "./app.categorysender.component";
import { ProductReceiverComponent } from "./app.productreceiver.component";
import { CommunicationService } from "./app.communication.service";
@NgModule({
    imports: [BrowserModule, FormsModule],
    declarations: [CategorySenderComponent, ProductReceiverComponent],
    providers: [CommunicationService],
    bootstrap: [CategorySenderComponent, ProductReceiverComponent]
})
export class AppModule { }

platformBrowserDynamic().bootstrapModule(AppModule);

The above code defines angular bootstrapping code. The code imports required Angular standard modules, the components and service created Step 2. The CommunicationService is defined in the providers array of the Angular module and the CategorySenderComponent and ProductReceiverComponent are defined as bootstrap components.

Step 4: We have index.html on the root of the workspace. Add the following mark-up in this file.

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <div class="container">
        <h1>
            <strong> Communication Across Components using Services </strong>
        </h1>
    </div>
    <table class="table table-bordered table-striped">
        <tr>
            <td>
                <app-catsender-component></app-catsender-component>
            </td>

        </tr>
        <tr>
            <td>
                <app-prdreceiver-component></app-prdreceiver-component>
            </td>
        </tr>
    </table>
</body>
</html>

The above mark-up uses selectors from the CategorySenderComponent and ProductReceiverComponent.

Step 5: Open Node.js command prompt, move to the folder that contains the code and run the following command from the Node.js command prompt

> npm run start

(Note: Please download the source code of the project, the package.js contains required settings for angular packages and other dependencies. The above command is defined in package.json)

This will run the application.

Open the browser and enter the following URL in the address bar. The browser will show result as shown in the following image

components-communication

Figure 3: Displaying Category and Products details

Click on the row of the Category table to select Category, the Product list will be updated that shows Products based on the selected Category Id

filter-result

Figure 4: Product shown based on the selected Category

Conclusion

In this tutorial we saw how Angular services can be used to communicate data across components using Event Subscription and Notifications.

Download the entire source code of this article (Github)

This article was technically reviewed by Ravi Kiran.

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook