Here we will discuss how to share data between sibling components using Rxjs Behavior Subject in Angular 5 project with step by step demonstration. As we know, we can share data between components in the different scenario like parent component to child component, child component to parent component, one component to another component which is siblings etc. And there are different ways available to share data between components like using Services, Event or Ngrx Store but these can be used as per their feasibility. Here we will only discuss how we can share data between siblings component. So, let start practical demonstration and see how to achieve this.
For this practical implementation, we will use API which we have already created in the previous article. Here we are using Asp.Net Core Web API with Oracle database. This API will show the list of employees and its details if we provide Employee Id as well. So, don't be confused if we make any HTTP call inside this demonstration. Please visit my previous article How to create Asp.Net Core Web API with Oracle database and Dapper ORM.
More about RxJS, Read Here.
Create Angular 5 Project
To create Angular 5 project, open Visual Studio Code [We can also use other editors like Visual Studio, Plnkr etc.] and press Ctrl+`. It will open a terminal window inside Visual Studio Code. Where we have to type following CLI command to create new Angular 5 project.
ng new BehaviorSubjectDemo --routing
To know more about how to create Angular 5 CLI project step by step, you can follow my previous article "Building Angular 5 CLI project with ngx-bootstrap". Once you execute the command it will create an Angular 5 CLI project for you. Now its time to run the project and see "is everything working fine?". To run the project, we have to fire one more command as follows.
ng serve --open
Below screenshot is the final architecture of this application after adding required components and service.
Add Bootstrap CDN
Once the project is ready and runs smoothly. First, we have to add bootstrap in the project so that we can easily access and implement bootstrap controls like textbox, dropdown etc. So, here I am not going to install bootstrap using "npm install bootstrap" process. To make it simple, just adding bootstrap CDN with Index.html page. We can also install bootstrap using NPM and use it to import it into the styles.scss file which is nothing but main CSS file. We can see that we have added bootstrap CDN path just before end of body tag.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BehaviorSubjectDemo</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
</body>
</html>
Create Employee Service
As we have already discussed, this application will use API which is already created and show the list of employees and employee details. So, let's create new service file in Angular 5 project inside service folder and name as "EmployeeService". This service is responsible for getting the list of data for the employee when a user clicks on the button "Get Employee List" and get the employee detail when a user clicks any of the employee rows from the table.
We can see with below code, here we have created one base URL, which is nothing but our API Base URL which is running locally. Apart from this, we have one method "getEmployeeList()" which will get the list of the employee from the Oracle database. Just move to next method which is "sendEmployeeDetail(id: number)". It takes one parameter as an employee id and gets the details for that particular employee.
Here we are using Next() function of the Rxjs/BehaviorSubject which set the current value of the object and if any observer is looking for it then it will update the value for that observer. The observer will not be directly calling "sendEmployeeDetail()" but it will be associated with some other method which is returning Observable data and the observer will subscribe to it.
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Employee } from "../data/employee";
import { Observable, BehaviorSubject } from "rxjs";
@Injectable()
export class EmployeeService {
private baseURL: string;
private empDetailSubject = new BehaviorSubject(null);
constructor(private http: HttpClient) {
this.baseURL = 'http://localhost:60769/';
}
getEmployeeList() {
return this.http.get<Employee[]>(this.baseURL + 'api/GetEmployeeList');
}
sendEmployeeDetail(id: number) {
let data = this.http.get<Employee>(this.baseURL + 'api/GetEmployeeDetails/'+id);
this.empDetailSubject.next(data);
}
getEmployeeDetail(){
return this.empDetailSubject.asObservable();
}
}
Enable CORS in Asp.Net Core Web API
As we using Asp.Net Core Web API project for creating API. To use it and getting data from API on your angular project, first, we have to enable CORS in the API. So, if you have created any Web API then you can enable CORS, but for me, we are using API created in the previous article. So, just go to that code and enable CORS adding following line of code.
app.UseCors(builder => builder.WithOrigins("http://localhost:4300").AllowAnyMethod().AllowAnyHeader());
using Core2API.Repositories;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Core2API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IEmployeeRepository, EmployeeRepository>();
services.AddSingleton<IConfiguration>(Configuration);
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Enable CORS
app.UseCors(builder => builder.WithOrigins("http://localhost:4300").AllowAnyMethod().AllowAnyHeader());
app.UseMvc();
}
}
}
Create Employee.ts
So, let create one interface which is nothing but defining the type of the result which will return from the database. Create an interface "Employee" with its properties as following.
export interface Employee{
ID: number,
NAME: string,
SALARY: number,
ADDRESS: string
}
Create two new components
Now we will create two new components as "EmployeeList" and "EmployeeDetail" using the following commands in "components" folder.
ng g c EmployeeList --save
ng g c EmployeeDetail --save
Update App.Module.ts
So, we have service ready which will bring data from a database and created two components which will display contents. Now we have to update our main module "AppModule" and add service in "Providers", so that service can access anywhere in components which are related to AppModule and components in "Declarations" section. As we can see with below code how we have added our components and service and imported from their location. Please kind attention with import statements, we are importing "HttpClientModule" for HTTP operation and "FormsModule" for using inbuilt directives like *ngFor, *ngIf etc.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { EmployeeListComponent } from './components/employee-list/employee-list.component';
import { EmployeeDetailComponent } from './components/employee-detail/employee-detail.component';
import { EmployeeService } from './sevices/employee.service';
@NgModule({
declarations: [
AppComponent,
EmployeeListComponent,
EmployeeDetailComponent
],
imports: [
BrowserModule,
HttpClientModule,
FormsModule
],
providers: [EmployeeService],
bootstrap: [AppComponent]
})
export class AppModule { }
App.component.html and App.component.ts
Move to "App.component.html", basically it's parent component. So, here we are going to add a button, which will get the list of the employee when we click on it and add "<app-employee-list></app-employee-list>" and <<app-employee-detail></app-employee-detail>" templates for showing the list of employee and employee's detail respectively.
So, basically "EmployeeListComponent" and "EmployeeDetailComponent" are the child component of "AppComponent".
<div class="container">
<div class="row">
<h2>Employee Portal</h2>
</div>
<div class="row">
<fieldset class="for-panel">
<legend>Contact Info</legend>
<div class="row">
<div class="col-sm-6">
<button (click)="getEmployeeList()" class="btn btn-primary">Get Employee List</button>
<br>
<br>
</div>
</div>
<div class="row"></div>
<div class="row">
<div class="col-sm-6">
<app-employee-list [empListData]="empList"></app-employee-list>
</div>
<div class="col-sm-6">
<app-employee-detail></app-employee-detail>
</div>
</div>
</fieldset>
</div>
</div>
With following AppComponent code, you can see, service is injected with a constructor. And the method "getEmployeeList()" is getting the list of employees from service and bind data to "empList" variable which is employee array type.
import { Component } from '@angular/core';
import { EmployeeService } from './sevices/employee.service';
import { Employee } from './data/employee';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
empList: Employee[];
constructor(private employeeService: EmployeeService) {
}
getEmployeeList() {
this.employeeService.getEmployeeList().subscribe(data => {
this.empList = data;
});
}
}
Employee-list.component.html and Employee-list.component.ts
Following code is for showing list of the employee in the tabular format. Here we are using a table for constructing header and its data something like a grid. In body part of the table, you will notice, to iterate the data, we are using *ngFor along with click event on the row. When we click on any row then click event will be fire and it will send the "employee id" for the selected row for getting individual detail of employee.
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Employee List</h3>
</div>
<table class="table table-hover" id="dev-table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Salary</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let item of empListData' (click)="sendEmployeeDetail(item.ID)">
<td>{{item.ID}}</td>
<td>{{item.NAME}}</td>
<td>{{item.SALARY}}</td>
<td>{{item.ADDRESS}}</td>
</tr>
</tbody>
</table>
</div>
Following is EmployeeListComponent class where you can notice that "empListData" is coming from parent components.
import { Component, OnInit, Input } from '@angular/core';
import { Employee } from '../../data/employee';
import { EmployeeService } from '../../sevices/employee.service';
//import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-employee-list',
templateUrl: './employee-list.component.html',
styleUrls: ['./employee-list.component.css']
})
export class EmployeeListComponent implements OnInit {
@Input() empListData: Employee[];
constructor(private employeeService: EmployeeService) {
}
sendEmployeeDetail(id: number){
this.employeeService.sendEmployeeDetail(id);
}
ngOnInit() {
}
}
Employee-detail.component.html and Employee-detail.component.ts
Now its time to show an individual detail of employee when we select or click any of the rows from the list of the employee. Here we are binding data from "employeeDetail" but as it is an observable so we can not directly access data. To access data from observable, we have to use aysnc pipe and provide some dummy name like "item" and then we will be able to access that data through item.
<div class="form-horizontal" *ngIf="employeeDetail | async as item">
<h3>Employee Detail</h3>
<br>
<label class="col-xs-4 control-label">ID: </label>
<p class="form-control-static">{{item[0].ID}}</p>
<label class="col-xs-4 control-label">NAME:</label>
<p class="form-control-static">{{item[0].NAME}}</p>
<label class="col-xs-4 control-label">SALARY:</label>
<p class="form-control-static">{{item[0].SALARY}}</p>
<label class="col-xs-4 control-label">ADDRESS:</label>
<p class="form-control-static">{{item[0].ADDRESS}}</p>
</div>
When clicking on any row from the list of employee, we load this employee detail page and its ngOnInit() event, which will get the employee details and bind with "employeeDetail" variable using the employee service.
import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../../sevices/employee.service';
import { Employee } from '../../data/employee';
@Component({
selector: 'app-employee-detail',
templateUrl: './employee-detail.component.html',
styleUrls: ['./employee-detail.component.css']
})
export class EmployeeDetailComponent implements OnInit {
employeeDetail: Employee;
constructor(private employeeService: EmployeeService) {
}
ngOnInit() {
this.employeeService.getEmployeeDetail().subscribe(data => { this.employeeDetail = data });
}
}
Here we have modified some css as following to look good table and all.
/* You can add global styles to this file, and also import other style files */
fieldset.for-panel {
background-color: #fcfcfc;
border: 1px solid #999;
border-radius: 4px;
padding:15px 10px;
background-color: #d9edf7;
border-color: #bce8f1;
background-color: #f9fdfd;
margin-bottom:12px;
}
fieldset.for-panel legend {
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 5px;
color: #4381ba;
font-size: 14px;
font-weight: bold;
line-height: 10px;
margin: inherit;
padding: 7px;
width: auto;
background-color: #d9edf7;
margin-bottom: 0;
}
Now run the application using the command "ng serve" and it will show the screen similar to below image if you have changed all CSS as per my demonstration. Here I am using port 4300 but by default, it is 4200. So, don't need to confuse for the port. Here you can see employee portal page, where we have a button "Get Employee List" and just below we have one table for employee List.
If we click on the button "Get Employee List" then it will get the data from the Oracle database using Asp.Net Core API and fill the following table as shown in the image below. So, now you can see we have four records showing inside the table.
Now let click any of the rows for employee detail. let say, I am clicking on which have employee id as 102. When I will click on that it will send the selected row employee id to the details page. And finally, data will be populated for employee detail just next to the list of the employee.
Conclusion
So, today we have learned how to share data between sibling components using rxjs behavior subject in angular 5 application.
I hope this post will help you. Please put your feedback using comment which helps me to improve myself for next post. If you have any doubts please ask your doubts or query in the comment section and If you like this post, please share it with your friends. Thanks