Real-time Angular 8 Application with SignalR and Cosmos DB

In this post, we will create a real-time application in SignalR with ASP.NET Core as backend and Angular 8 as frontend. We will use Cosmos DB to save and retrieve the data. We will create a fully functioned employee application to illustrate the real-time features.

 

Introduction

 
SignalR is a library for ASP.NET developers to simplify the process of adding real-time web functionality to applications. Real-time web functionality is the ability to have server code push content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data. Chat application is often used as SignalR example, but I have already written an article on C# Corner with MVC and SignalR. I have received a very good response from readers on that article. Many people asked me to write an article using SignalR and Angular. In this article, we will create a fully functioned employee application. We will create the backend of this application in ASP.NET Core using SignalR. We will use Cosmos DB to save and retrieve the employee data. We will create a SignalR hub and broadcast message to the connected clients. We will then create an Angular 8 application using Angular CLI and create all components for listing, inserting and editing employee data. We will get the broadcasted message from SignalR hub and immediately show the updated data on client screen. Whenever a user adds/edit/delete an employee record, it will immediately reflect on all other browsers without any user intervention. We can see all the actions step by step.
 

Create a database and collection in Cosmos DB

 
As we are saving and retrieving data to Cosmos DB, we can use Cosmos DB emulator to test our application locally. Emulator is fully free, and you can download the MSI setup from this link.
 
After the download and installation of the emulator, you can run it and create a new database and a collection for your application.
 
 
 
 
You can see a URI and Primary key in this emulator. We will use these values later with our application to connect Cosmos DB. You can click the Explorer tab and create a new database and a collection.
 
Since we are creating an Employee application; we need to create a collection named “Employee”. (In Azure Cosmos DB, collections are now called as containers)
 
 
 
 
We can give a name to our database and collection id as well. You must give a Partition key. A Partition key is very important in Cosmos DB in order to store the data.
 
Please note, you can’t change the partition key once you created the collection. You must be very careful to choose the correct partition key. Unlike SQL database, there is no need to create other schemas in design time. Data will be saved in JSON format with appropriate field names in run time.
 

Create an ASP.NET Core Web API project in Visual Studio

 
We can create an ASP.NET core web application project using Web API template. I am using visual studio 2019 version.
 
 
We need two external libraries in this project. One for Cosmos DB and another for SignalR. Both are provided by Microsoft only. We can install these libraries one by one using “Manage NuGet Packages” option.
 
First, we can install “Microsoft.Azure.DocumentDB.Core” library for Cosmos DB. You can choose the latest version and install.
 
 
 
Then, we can install “Microsoft.AspNet.SignalR” library. This will be used for broadcasting messages to the connected clients real-time.
 
 
 
 
We can create an interface for SignalR hub client.
 
IHubClient.cs
  1. using System.Threading.Tasks;  
  2.   
  3. namespace SignalREmployee  
  4. {  
  5.     public interface IHubClient  
  6.     {  
  7.         Task BroadcastMessage();  
  8.     }  
  9. }  
We have created a simple method declaration inside above interface. Please note, in this application we will just notify the client, when a transaction update is happened. You can pass any type of message as a parameter also.
 
We can create a class “BroadcastHub” and inherit “Hub” class with above interface “IHubClient”
 
BroadcastHub.cs
  1. using Microsoft.AspNetCore.SignalR;  
  2.   
  3. namespace SignalREmployee  
  4. {  
  5.     public class BroadcastHub : Hub<IHubClient>  
  6.     {  
  7.     }  
  8. }  
We will use this class in our web api controller class and broadcast message to connected clients after updating the employee data in create, update, and delete events later.
 

Create Cosmos DB Web API service with Repository pattern

 
We can create the Web API service for our angular application in ASP.NET core with Cosmos DB database. Since we are creating Employee application, we can create an “Employee” model class first.
 
Create a “Data” folder in the root and create “Employee” class inside it. You can copy the below code and paste to this class file.
 
Employee.cs
  1. using Newtonsoft.Json;  
  2.   
  3. namespace SignalREmployee.Data  
  4. {  
  5.     public class Employee  
  6.     {  
  7.         [JsonProperty(PropertyName = "id")]  
  8.         public string Id { getset; }  
  9.         public string Name { getset; }  
  10.         public string Address { getset; }  
  11.         public string Gender { getset; }  
  12.         public string Company { getset; }  
  13.         public string Designation { getset; }  
  14.         public string Cityname { getset; }  
  15.     }  
  16. }  
I have added all the field names required for our Cosmos DB collection. Also note that I have added a “JsonProperty” attribute for “Id” property. Because, Cosmos DB automatically creates an “id” field for each record.
 
We can create an “IDocumentDBRepository” interface inside the “Data” folder. This interface contains all the method names for our Cosmos DB repository. We will implement this interface in the “DocumentDBRepository” class.
 
IDocumentDBRepository.cs
  1. using Microsoft.Azure.Documents;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq.Expressions;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace SignalREmployee.Data  
  8. {  
  9.     public interface IDocumentDBRepository<T> where T : class  
  10.     {  
  11.         Task<Document> CreateItemAsync(T item, string collectionId);  
  12.         Task DeleteItemAsync(string id, string collectionId, string partitionKey);  
  13.         Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate, string collectionId);  
  14.         Task<IEnumerable<T>> GetItemsAsync(string collectionId);  
  15.         Task<Document> UpdateItemAsync(string id, T item, string collectionId);  
  16.     }  
  17. }  
I have added all the methods declaration for CRUD actions for Web API controller in the above interface.
We can implement this interface in the “DocumentDBRepository” class.
 
DocumentDBRepository.cs
  1. using Microsoft.Azure.Documents;  
  2. using Microsoft.Azure.Documents.Client;  
  3. using Microsoft.Azure.Documents.Linq;  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Linq;  
  7. using System.Linq.Expressions;  
  8. using System.Threading.Tasks;  
  9.   
  10. namespace SignalREmployee.Data  
  11. {  
  12.     public class DocumentDBRepository<T> : IDocumentDBRepository<T> where T : class  
  13.     {  
  14.   
  15.         private readonly string Endpoint = "https://localhost:8081/";  
  16.         private readonly string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";  
  17.         private readonly string DatabaseId = "SarathCosmosDB";  
  18.         private readonly DocumentClient client;  
  19.   
  20.         public DocumentDBRepository()  
  21.         {  
  22.             client = new DocumentClient(new Uri(Endpoint), Key);  
  23.         }  
  24.   
  25.         public async Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate, string collectionId)  
  26.         {  
  27.             IDocumentQuery<T> query = client.CreateDocumentQuery<T>(  
  28.                 UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId),  
  29.                 new FeedOptions { MaxItemCount = -1 })  
  30.                 .Where(predicate)  
  31.                 .AsDocumentQuery();  
  32.   
  33.             List<T> results = new List<T>();  
  34.             while (query.HasMoreResults)  
  35.             {  
  36.                 results.AddRange(await query.ExecuteNextAsync<T>());  
  37.             }  
  38.   
  39.             return results;  
  40.         }  
  41.   
  42.         public async Task<IEnumerable<T>> GetItemsAsync(string collectionId)  
  43.         {  
  44.             IDocumentQuery<T> query = client.CreateDocumentQuery<T>(  
  45.                 UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId),  
  46.                 new FeedOptions { MaxItemCount = -1 })  
  47.                 .AsDocumentQuery();  
  48.   
  49.             List<T> results = new List<T>();  
  50.             while (query.HasMoreResults)  
  51.             {  
  52.                 results.AddRange(await query.ExecuteNextAsync<T>());  
  53.             }  
  54.   
  55.             return results;  
  56.         }  
  57.   
  58.         public async Task<Document> CreateItemAsync(T item, string collectionId)  
  59.         {  
  60.             return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId), item);  
  61.         }  
  62.   
  63.         public async Task<Document> UpdateItemAsync(string id, T item, string collectionId)  
  64.         {  
  65.             return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, collectionId, id), item);  
  66.         }  
  67.   
  68.         public async Task DeleteItemAsync(string id, string collectionId, string partitionKey)  
  69.         {  
  70.             await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, collectionId, id),  
  71.             new RequestOptions() { PartitionKey = new PartitionKey(partitionKey) });  
  72.         }  
  73.     }  
  74. }  
I have implemented all the CRUD actions inside the above class. We can use these methods in our Web API controller.
We can create “EmployeesController” controller class now.
 
EmployeesController.cs
  1. using Microsoft.AspNetCore.Mvc;  
  2. using Microsoft.AspNetCore.SignalR;  
  3. using SignalREmployee.Data;  
  4. using System.Collections.Generic;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace SignalREmployee.Controllers  
  8. {  
  9.     [Route("api/[controller]")]  
  10.     [ApiController]  
  11.     public class EmployeesController : ControllerBase  
  12.     {  
  13.         private readonly IDocumentDBRepository<Employee> Respository;  
  14.         private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;  
  15.         private readonly string CollectionId;  
  16.   
  17.         public EmployeesController(  
  18.             IDocumentDBRepository<Employee> Respository,   
  19.             IHubContext<BroadcastHub, IHubClient> hubContext)  
  20.         {  
  21.             _hubContext = hubContext;  
  22.             this.Respository = Respository;  
  23.             CollectionId = "Employee";  
  24.         }  
  25.   
  26.         [HttpGet]  
  27.         public async Task<IEnumerable<Employee>> Get()  
  28.         {  
  29.             return await Respository.GetItemsAsync(CollectionId);  
  30.         }  
  31.   
  32.         [HttpGet("{id}/{cityname}")]  
  33.         public async Task<Employee> Get(string id, string cityname)  
  34.         {  
  35.             var employees = await Respository.GetItemsAsync(d => d.Id == id && d.Cityname == cityname, CollectionId);  
  36.             Employee employee = new Employee();  
  37.             foreach (var emp in employees)  
  38.             {  
  39.                 employee = emp;  
  40.                 break;  
  41.             }  
  42.             return employee;  
  43.         }  
  44.   
  45.         [HttpPost]  
  46.         public async Task<bool> Post([FromBody]Employee employee)  
  47.         {  
  48.             try  
  49.             {  
  50.                 if (ModelState.IsValid)  
  51.                 {  
  52.                     employee.Id = null;  
  53.                     await Respository.CreateItemAsync(employee, CollectionId);  
  54.                     await _hubContext.Clients.All.BroadcastMessage();  
  55.                 }  
  56.                 return true;  
  57.             }  
  58.             catch  
  59.             {  
  60.                 return false;  
  61.             }  
  62.   
  63.         }  
  64.   
  65.         [HttpPut]  
  66.         public async Task<bool> Put([FromBody]Employee employee)  
  67.         {  
  68.             try  
  69.             {  
  70.                 if (ModelState.IsValid)  
  71.                 {  
  72.                     await Respository.UpdateItemAsync(employee.Id, employee, CollectionId);  
  73.                     await _hubContext.Clients.All.BroadcastMessage();  
  74.                 }  
  75.                 return true;  
  76.             }  
  77.             catch  
  78.             {  
  79.                 return false;  
  80.             }  
  81.         }  
  82.   
  83.         [HttpDelete("{id}/{cityname}")]  
  84.         public async Task<bool> Delete(string id, string cityname)  
  85.         {  
  86.             try  
  87.             {  
  88.                 await Respository.DeleteItemAsync(id, CollectionId, cityname);  
  89.                 await _hubContext.Clients.All.BroadcastMessage();  
  90.                 return true;  
  91.             }  
  92.             catch  
  93.             {  
  94.                 return false;  
  95.             }  
  96.         }  
  97.     }  
  98. }  
I have implemented all the action methods inside this controller class using DocumentDBRepository class. Also note that, we have injected repository class and IHubContext of SignalR in the controller constructor.
 
 
 
We have also called the “BroadcastMessage” in Post, Put, and Delete methods after saving the data to Cosmos DB database using repository.
 
 
 
We are calling above API service from Angular application. Web API application and Angular application are running on different ports or different servers (in production). Hence, we can add CORS headers in Startup class. We must create a client notification service using SignalR also in this class.
 
We can inject the dependency to DocumentDBRepository service inside Startup class using a singleton pattern.
 
Startup.cs
  1. using Microsoft.AspNetCore.Builder;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.AspNetCore.Mvc;  
  4. using Microsoft.Extensions.Configuration;  
  5. using Microsoft.Extensions.DependencyInjection;  
  6. using SignalREmployee.Data;  
  7.   
  8. namespace SignalREmployee  
  9. {  
  10.     public class Startup  
  11.     {  
  12.         public Startup(IConfiguration configuration)  
  13.         {  
  14.             Configuration = configuration;  
  15.         }  
  16.   
  17.         public IConfiguration Configuration { get; }  
  18.   
  19.         // This method gets called by the runtime. Use this method to add services to the container.  
  20.         public void ConfigureServices(IServiceCollection services)  
  21.         {  
  22.             services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {  
  23.                 builder  
  24.                 .AllowAnyMethod()  
  25.                 .AllowAnyHeader()  
  26.                 .AllowCredentials()  
  27.                 .WithOrigins("http://localhost:4200");  
  28.             }));  
  29.   
  30.             services.AddSignalR();  
  31.   
  32.             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
  33.   
  34.             services.AddSingleton<IDocumentDBRepository<Employee>>(new DocumentDBRepository<Employee>());  
  35.         }  
  36.   
  37.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  38.         public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
  39.         {  
  40.             if (env.IsDevelopment())  
  41.             {  
  42.                 app.UseDeveloperExceptionPage();  
  43.             }  
  44.   
  45.             app.UseCors("CorsPolicy");  
  46.             app.UseSignalR(routes =>  
  47.             {  
  48.                 routes.MapHub<BroadcastHub>("/notify");  
  49.             });  
  50.   
  51.             app.UseMvc();  
  52.         }  
  53.     }  
  54. }  
We have modified the “ConfigureServices” and “Configure” methods in above class.
We have successfully created our Web API service. If needed, you can check the API with Postman or any other tool.
 

Create Angular 8 application using CLI

 
We can create the angular application with all components using Angular CLI commands. I will explain all the steps one by one.
Use below command to create new Angular application.
 
ng new AngularEmployee
 
We are choosing angular routing for this application. It will take some time to create all node modules for our Angular application. Once, our application is ready, we can install below node packages into our application.
 
npm install @aspnet/signalr
npm install bootstrap
npm install font-awesome
 
We have now installed signalr client, bootstrap, and font-awesome packages to our application.
We must modify styles.css file in the root folder with below changes to access these packages globally in the application without further references.
 
styles.css
  1. @import "~bootstrap/dist/css/bootstrap.css";    
  2. @import "~font-awesome/css/font-awesome.css";    
Use below command to create a new “Home” component.
 
ng g component home
 
Modify the html template file with below code.
 
home.component.html
  1. <div style="text-align:center;">  
  2.     <h1>Real-time Angular 8 Application with SignalR and Cosmos DB</h1>  
  3.     <p>Welcome to our new single-page application, built with below technologies:</p>  
  4.     <img src="../../assets/Angular with Cosmos DB and SignalR.png" />  
  5. </div>  
We can create a small navigation menu in our application
 
ng g component nav-menu
 
Modify the template file with below code.
 
nav-menu.component.html
  1. <header>  
  2.     <nav class='navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'>  
  3.       <div class="container">  
  4.         <a class="navbar-brand" [routerLink]='["/"]'>Employee App</a>  
  5.         <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-label="Toggle navigation"  
  6.                 [attr.aria-expanded]="isExpanded" (click)="toggle()">  
  7.           <span class="navbar-toggler-icon"></span>  
  8.         </button>  
  9.         <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='{"show": isExpanded}'>  
  10.           <ul class="navbar-nav flex-grow">  
  11.             <li class="nav-item" [routerLinkActive]='["link-active"]' [routerLinkActiveOptions]='{ exact: true }'>  
  12.               <a class="nav-link text-dark" [routerLink]='["/"]'>Home</a>  
  13.             </li>  
  14.             <li class="nav-item" [routerLinkActive]='["link-active"]'>  
  15.               <a class="nav-link text-dark" [routerLink]='["/employees"]'>Employees</a>  
  16.             </li>  
  17.           </ul>  
  18.         </div>  
  19.       </div>  
  20.     </nav>  
  21.   </header>  
  22.   <footer>  
  23.     <nav class="navbar navbar-light bg-white mt-5 fixed-bottom">  
  24.       <div class="navbar-expand m-auto navbar-text">  
  25.         Developed with <i class="fa fa-heart"></i> by <a href="https://codewithsarath.com" target="_blank"><b>Sarathlal Saseendran</b></a>  
  26.       </div>  
  27.     </nav>  
  28.   </footer>  
  29.     
Modify the component file with below code.
 
nav-menu.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'app-nav-menu',  
  5.   templateUrl: './nav-menu.component.html',  
  6.   styleUrls: ['./nav-menu.component.css']  
  7. })  
  8. export class NavMenuComponent implements OnInit {  
  9.   
  10.   constructor() { }  
  11.   isExpanded = false;  
  12.   
  13.   ngOnInit() {  
  14.   }  
  15.   
  16.   collapse() {  
  17.     this.isExpanded = false;  
  18.   }  
  19.   
  20.   toggle() {  
  21.     this.isExpanded = !this.isExpanded;  
  22.   }  
  23. }  
We need some styles also in the style file. Modify the style file with below code.
 
nav-menu.component.css
  1. a.navbar-brand {  
  2.     white-spacenormal;  
  3.     text-aligncenter;  
  4.     word-break: break-all;  
  5. }  
  6.   
  7. html {  
  8.     font-size14px;  
  9. }  
  10.   
  11. @media (min-width768px) {  
  12.     html {  
  13.         font-size16px;  
  14.     }  
  15. }  
  16.   
  17. .box-shadow {  
  18.     box-shadow: 0 .25rem .75rem rgba(000, .05);  
  19. }  
  20.   
  21. .fa-heart {  
  22.     color: hotpink;  
  23. }  
We can create a generic validator class to validate the employee name.
 
ng g class shared\generic-validator
 
Copy below code and paste to the class file.
 
generic-validator.ts
  1. import { FormGroup } from '@angular/forms';    
  2.     
  3. export class GenericValidator {    
  4.     
  5.   constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {    
  6.   }    
  7.     
  8.   processMessages(container: FormGroup): { [key: string]: string } {    
  9.     const messages = {};    
  10.     for (const controlKey in container.controls) {    
  11.       if (container.controls.hasOwnProperty(controlKey)) {    
  12.         const c = container.controls[controlKey];    
  13.         // If it is a FormGroup, process its child controls.    
  14.         if (c instanceof FormGroup) {    
  15.           const childMessages = this.processMessages(c);    
  16.           Object.assign(messages, childMessages);    
  17.         } else {    
  18.           // Only validate if there are validation messages for the control    
  19.           if (this.validationMessages[controlKey]) {    
  20.             messages[controlKey] = '';    
  21.             if ((c.dirty || c.touched) && c.errors) {    
  22.               Object.keys(c.errors).map(messageKey => {    
  23.                 if (this.validationMessages[controlKey][messageKey]) {    
  24.                   messages[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';    
  25.                 }    
  26.               });    
  27.             }    
  28.           }    
  29.         }    
  30.       }    
  31.     }    
  32.     return messages;    
  33.   }    
  34.     
  35.   getErrorCount(container: FormGroup): number {    
  36.     let errorCount = 0;    
  37.     for (const controlKey in container.controls) {    
  38.       if (container.controls.hasOwnProperty(controlKey)) {    
  39.         if (container.controls[controlKey].errors) {    
  40.           errorCount += Object.keys(container.controls[controlKey].errors).length;    
  41.           console.log(errorCount);    
  42.         }    
  43.       }    
  44.     }    
  45.     return errorCount;    
  46.   }    
  47. }    
We can create the employee interface using below command.
 
ng g interface employee\Employee
 
Copy below code and paste to the interface file.
 
employee.ts
  1. export interface Employee {  
  2.     id: string,  
  3.     name: string,  
  4.     address: string,  
  5.     gender: string,  
  6.     company: string,  
  7.     designation: string,  
  8.     cityname: string  
  9. }    
We can create the very important Angular service using below command.
ng g service employee\Employee
We can add all the CRUD logic inside this service class.
employee.service.ts
  1. import { Injectable, Inject } from '@angular/core';    
  2. import { HttpClient, HttpHeaders } from '@angular/common/http';    
  3. import { Observable, throwError, of } from 'rxjs';    
  4. import { catchError, map } from 'rxjs/operators';    
  5. import { Employee } from './employee';  
  6.     
  7. @Injectable()    
  8. export class EmployeeService {    
  9.   private employeesUrl = 'http://localhost:57400/api/employees';    
  10.     
  11.   constructor(private http: HttpClient) { }    
  12.     
  13.   getEmployees(): Observable<Employee[]> {    
  14.     return this.http.get<Employee[]>(this.employeesUrl)    
  15.       .pipe(    
  16.         catchError(this.handleError)    
  17.       );    
  18.   }    
  19.     
  20.   getEmployee(id: string, cityName: string): Observable<Employee> {    
  21.     if (id === '') {    
  22.       return of(this.initializeEmployee());    
  23.     }    
  24.     const url = `${this.employeesUrl}/${id}/${cityName}`;    
  25.     return this.http.get<Employee>(url)    
  26.       .pipe(    
  27.         catchError(this.handleError)    
  28.       );    
  29.   }    
  30.     
  31.   createEmployee(employee: Employee): Observable<Employee> {    
  32.     const headers = new HttpHeaders({ 'Content-Type''application/json' });    
  33.     return this.http.post<Employee>(this.employeesUrl, employee, { headers: headers })    
  34.       .pipe(    
  35.         catchError(this.handleError)    
  36.       );    
  37.   }    
  38.     
  39.   deleteEmployee(id: string, cityname: string): Observable<{}> {    
  40.     const headers = new HttpHeaders({ 'Content-Type''application/json' });    
  41.     const url = `${this.employeesUrl}/${id}/${cityname}`;    
  42.     return this.http.delete<Employee>(url, { headers: headers })    
  43.       .pipe(    
  44.         catchError(this.handleError)    
  45.       );    
  46.   }    
  47.     
  48.   updateEmployee(employee: Employee): Observable<Employee> {    
  49.     const headers = new HttpHeaders({ 'Content-Type''application/json' });    
  50.     const url = this.employeesUrl;    
  51.     return this.http.put<Employee>(url, employee, { headers: headers })    
  52.       .pipe(    
  53.         map(() => employee),    
  54.         catchError(this.handleError)    
  55.       );    
  56.   }    
  57.     
  58.   private handleError(err) {    
  59.     let errorMessage: string;    
  60.     if (err.error instanceof ErrorEvent) {    
  61.       errorMessage = `An error occurred: ${err.error.message}`;    
  62.     } else {    
  63.       errorMessage = `Backend returned code ${err.status}: ${err.body.error}`;    
  64.     }    
  65.     console.error(err);    
  66.     return throwError(errorMessage);    
  67.   }    
  68.     
  69.   private initializeEmployee(): Employee {    
  70.     return {    
  71.       id: null,    
  72.       name: null,    
  73.       address: null,    
  74.       gender: null,    
  75.       company: null,    
  76.       designation: null,    
  77.       cityname: null    
  78.     };    
  79.   }    
  80. }    
We are connecting our ASP.NET Core API in this service. Please modify the port number with your own port number.
We can create the “EmployeeList” component to list all employee data.
 
ng g component employee\EmployeeList
 
Modify the component file with below code.
 
employee-list.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2. import { Employee } from '../employee';  
  3. import { EmployeeService } from '../employee.service';  
  4. import * as signalR from '@aspnet/signalr';  
  5.   
  6. @Component({  
  7.   selector: 'app-employee-list',  
  8.   templateUrl: './employee-list.component.html',  
  9.   styleUrls: ['./employee-list.component.css']  
  10. })  
  11. export class EmployeeListComponent implements OnInit {  
  12.   pageTitle = 'Employee List';  
  13.   filteredEmployees: Employee[] = [];  
  14.   employees: Employee[] = [];  
  15.   errorMessage = '';  
  16.   
  17.   _listFilter = '';  
  18.   get listFilter(): string {  
  19.     return this._listFilter;  
  20.   }  
  21.   set listFilter(value: string) {  
  22.     this._listFilter = value;  
  23.     this.filteredEmployees = this.listFilter ? this.performFilter(this.listFilter) : this.employees;  
  24.   }  
  25.   
  26.   constructor(private employeeService: EmployeeService) { }  
  27.   
  28.   performFilter(filterBy: string): Employee[] {  
  29.     filterBy = filterBy.toLocaleLowerCase();  
  30.     return this.employees.filter((employee: Employee) =>  
  31.       employee.name.toLocaleLowerCase().indexOf(filterBy) !== -1);  
  32.   }  
  33.   
  34.   ngOnInit(): void {  
  35.     this.getEmployeeData();  
  36.   
  37.     const connection = new signalR.HubConnectionBuilder()  
  38.       .configureLogging(signalR.LogLevel.Information)  
  39.       .withUrl("http://localhost:57400/notify")  
  40.       .build();  
  41.   
  42.     connection.start().then(function () {  
  43.       console.log('SignalR Connected!');  
  44.     }).catch(function (err) {  
  45.       return console.error(err.toString());  
  46.     });  
  47.   
  48.     connection.on("BroadcastMessage", () => {  
  49.       this.getEmployeeData();  
  50.     });  
  51.   }  
  52.   
  53.   getEmployeeData() {  
  54.     this.employeeService.getEmployees().subscribe(  
  55.       employees => {  
  56.         this.employees = employees;  
  57.         this.filteredEmployees = this.employees;  
  58.       },  
  59.       error => this.errorMessage = <any>error  
  60.     );  
  61.   }  
  62.   
  63.   deleteEmployee(id: string, name: string, cityname: string): void {  
  64.     if (id === '') {  
  65.       this.onSaveComplete();  
  66.     } else {  
  67.       if (confirm(`Are you sure want to delete this Employee: ${name}?`)) {  
  68.         this.employeeService.deleteEmployee(id, cityname)  
  69.           .subscribe(  
  70.             () => this.onSaveComplete(),  
  71.             (error: any) => this.errorMessage = <any>error  
  72.           );  
  73.       }  
  74.     }  
  75.   }  
  76.   
  77.   onSaveComplete(): void {  
  78.     this.employeeService.getEmployees().subscribe(  
  79.       employees => {  
  80.         this.employees = employees;  
  81.         this.filteredEmployees = this.employees;  
  82.       },  
  83.       error => this.errorMessage = <any>error  
  84.     );  
  85.   }  
  86.   
  87. }    
We have added a SignalR connection in the ngOnInit method and listening to the notification from SignalR server hub. Whenever a new data created/updated/deleted on server side, client will automatically get notification of that change and call the getEmployeeData method. So that, the employee list will be automatically refreshed without any user intervention.
 
 
 
We can modify the html template file with below code.
 
employee-list.component.html
  1. <div class="card">  
  2.     <div class="card-header">  
  3.         {{pageTitle}}  
  4.     </div>  
  5.     <div class="card-body">  
  6.         <div class="row" style="margin-bottom:15px;">  
  7.             <div class="col-md-2">Filter by:</div>  
  8.             <div class="col-md-4">  
  9.                 <input type="text" [(ngModel)]="listFilter" />  
  10.             </div>  
  11.             <div class="col-md-2"></div>  
  12.             <div class="col-md-4">  
  13.                 <button class="btn btn-primary mr-3" [routerLink]="['/employees/0/0/edit']">  
  14.                     New Employee  
  15.                 </button>  
  16.             </div>  
  17.         </div>  
  18.         <div class="row" *ngIf="listFilter">  
  19.             <div class="col-md-6">  
  20.                 <h4>Filtered by: {{listFilter}}</h4>  
  21.             </div>  
  22.         </div>  
  23.         <div class="table-responsive">  
  24.             <table class="table mb-0" *ngIf="employees && employees.length">  
  25.                 <thead>  
  26.                     <tr>  
  27.                         <th>Name</th>  
  28.                         <th>Address</th>  
  29.                         <th>Gender</th>  
  30.                         <th>Company</th>  
  31.                         <th>Designation</th>  
  32.                         <th></th>  
  33.                         <th></th>  
  34.                     </tr>  
  35.                 </thead>  
  36.                 <tbody>  
  37.                     <tr *ngFor="let employee of filteredEmployees">  
  38.                         <td>  
  39.                             <a [routerLink]="['/employees', employee.id,employee.cityname]">  
  40.                                 {{ employee.name }}  
  41.                             </a>  
  42.                         </td>  
  43.                         <td>{{ employee.address }}</td>  
  44.                         <td>{{ employee.gender }}</td>  
  45.                         <td>{{ employee.company }}</td>  
  46.                         <td>{{ employee.designation}} </td>  
  47.                         <td>  
  48.                             <button class="btn btn-outline-primary btn-sm"  
  49.                                 [routerLink]="['/employees', employee.id, employee.cityname, 'edit']">  
  50.                                 Edit  
  51.                             </button>  
  52.                         </td>  
  53.                         <td>  
  54.                             <button class="btn btn-outline-warning btn-sm"  
  55.                                 (click)="deleteEmployee(employee.id,  employee.name,employee.cityname);">  
  56.                                 Delete  
  57.                             </button>  
  58.                         </td>  
  59.                     </tr>  
  60.                 </tbody>  
  61.             </table>  
  62.         </div>  
  63.     </div>  
  64. </div>  
  65. <div *ngIf="errorMessage" class="alert alert-danger">  
  66.     Error: {{ errorMessage }}  
  67. </div>  
Also modify the style file also.
 
employee-list.component.css
  1. thead {  
  2.     color#337AB7;  
  3. }  
We can create “EmployeeEdit” component.
 
ng g component employee\EmployeeEdit
 
Modify the component with below code.
 
employee-edit.component.ts
  1. import { Component, OnInit, AfterViewInit, OnDestroy, ElementRef, ViewChildren } from '@angular/core';  
  2. import { FormControlName, FormGroup, FormBuilder, Validators } from '@angular/forms';  
  3. import { Subscription } from 'rxjs';  
  4. import { ActivatedRoute, Router } from '@angular/router';  
  5. import { GenericValidator } from 'src/app/shared/generic-validator';  
  6. import { Employee } from '../employee';  
  7. import { EmployeeService } from '../employee.service';  
  8.   
  9. @Component({  
  10.   selector: 'app-employee-edit',  
  11.   templateUrl: './employee-edit.component.html',  
  12.   styleUrls: ['./employee-edit.component.css']  
  13. })  
  14. export class EmployeeEditComponent implements OnInit, OnDestroy {  
  15.   @ViewChildren(FormControlName, { read: ElementRef }) formInputElements: ElementRef[];  
  16.   pageTitle = 'Employee Edit';  
  17.   errorMessage: string;  
  18.   employeeForm: FormGroup;  
  19.   tranMode: string;  
  20.   employee: Employee;  
  21.   private sub: Subscription;  
  22.   
  23.   displayMessage: { [key: string]: string } = {};  
  24.   private validationMessages: { [key: string]: { [key: string]: string } };  
  25.   private genericValidator: GenericValidator;  
  26.   
  27.   
  28.   constructor(private fb: FormBuilder,  
  29.     private route: ActivatedRoute,  
  30.     private router: Router,  
  31.     private employeeService: EmployeeService) {  
  32.   
  33.     this.validationMessages = {  
  34.       name: {  
  35.         required: 'Employee name is required.',  
  36.         minlength: 'Employee name must be at least three characters.',  
  37.         maxlength: 'Employee name cannot exceed 50 characters.'  
  38.       },  
  39.       cityname: {  
  40.         required: 'Employee city name is required.',  
  41.       }  
  42.     };  
  43.     this.genericValidator = new GenericValidator(this.validationMessages);  
  44.   }  
  45.   
  46.   ngOnInit() {  
  47.     this.tranMode = "new";  
  48.     this.employeeForm = this.fb.group({  
  49.       name: ['', [Validators.required,  
  50.       Validators.minLength(3),  
  51.       Validators.maxLength(50)  
  52.       ]],  
  53.       address: '',  
  54.       cityname: ['', [Validators.required]],  
  55.       gender: '',  
  56.       company: '',  
  57.       designation: '',  
  58.     });  
  59.   
  60.     this.sub = this.route.paramMap.subscribe(  
  61.       params => {  
  62.         const id = params.get('id');  
  63.         const cityname = params.get('cityname');  
  64.         if (id == '0') {  
  65.           const employee: Employee = { id: "0", name: "", address: "", gender: "", company: "", designation: "", cityname: "" };  
  66.           this.displayEmployee(employee);  
  67.         }  
  68.         else {  
  69.           this.getEmployee(id, cityname);  
  70.         }  
  71.       }  
  72.     );  
  73.   }  
  74.   
  75.   ngOnDestroy(): void {  
  76.     this.sub.unsubscribe();  
  77.   }  
  78.   
  79.   getEmployee(id: string, cityname: string): void {  
  80.     this.employeeService.getEmployee(id, cityname)  
  81.       .subscribe(  
  82.         (employee: Employee) => this.displayEmployee(employee),  
  83.         (error: any) => this.errorMessage = <any>error  
  84.       );  
  85.   }  
  86.   
  87.   displayEmployee(employee: Employee): void {  
  88.     if (this.employeeForm) {  
  89.       this.employeeForm.reset();  
  90.     }  
  91.     this.employee = employee;  
  92.     if (this.employee.id == '0') {  
  93.       this.pageTitle = 'Add Employee';  
  94.     } else {  
  95.       this.pageTitle = `Edit Employee: ${this.employee.name}`;  
  96.     }  
  97.     this.employeeForm.patchValue({  
  98.       name: this.employee.name,  
  99.       address: this.employee.address,  
  100.       gender: this.employee.gender,  
  101.       company: this.employee.company,  
  102.       designation: this.employee.designation,  
  103.       cityname: this.employee.cityname  
  104.     });  
  105.   }  
  106.   
  107.   deleteEmployee(): void {  
  108.     if (this.employee.id == '0') {  
  109.       this.onSaveComplete();  
  110.     } else {  
  111.       if (confirm(`Are you sure want to delete this Employee: ${this.employee.name}?`)) {  
  112.         this.employeeService.deleteEmployee(this.employee.id, this.employee.cityname)  
  113.           .subscribe(  
  114.             () => this.onSaveComplete(),  
  115.             (error: any) => this.errorMessage = <any>error  
  116.           );  
  117.       }  
  118.     }  
  119.   }  
  120.   
  121.   saveEmployee(): void {  
  122.     if (this.employeeForm.valid) {  
  123.       if (this.employeeForm.dirty) {  
  124.         const p = { ...this.employee, ...this.employeeForm.value };  
  125.         if (p.id === '0') {  
  126.           this.employeeService.createEmployee(p)  
  127.             .subscribe(  
  128.               () => this.onSaveComplete(),  
  129.               (error: any) => this.errorMessage = <any>error  
  130.             );  
  131.         } else {  
  132.           this.employeeService.updateEmployee(p)  
  133.             .subscribe(  
  134.               () => this.onSaveComplete(),  
  135.               (error: any) => this.errorMessage = <any>error  
  136.             );  
  137.         }  
  138.       } else {  
  139.         this.onSaveComplete();  
  140.       }  
  141.     } else {  
  142.       this.errorMessage = 'Please correct the validation errors.';  
  143.     }  
  144.   }  
  145.   
  146.   onSaveComplete(): void {  
  147.     this.employeeForm.reset();  
  148.     this.router.navigate(['/employees']);  
  149.   }  
  150. }    
Modify the template file with below code.
 
employee-edit.component.html
  1. <div class="card">    
  2.     <div class="card-header">    
  3.       {{pageTitle}}    
  4.     </div>    
  5.       
  6.     <div class="card-body">    
  7.       <form novalidate    
  8.             (ngSubmit)="saveEmployee()"    
  9.             [formGroup]="employeeForm">    
  10.       
  11.         <div class="form-group row mb-2">    
  12.           <label class="col-md-3 col-form-label"    
  13.                  for="employeeNameId">Employee Name</label>    
  14.           <div class="col-md-7">    
  15.             <input class="form-control"    
  16.                    id="employeeNameId"    
  17.                    type="text"    
  18.                    placeholder="Name (required)"    
  19.                    formControlName="name"    
  20.                    [ngClass]="{'is-invalid': displayMessage.name }" />    
  21.             <span class="invalid-feedback">    
  22.               {{displayMessage.name}}    
  23.             </span>    
  24.           </div>    
  25.         </div>    
  26.       
  27.         <div class="form-group row mb-2">    
  28.           <label class="col-md-3 col-form-label"    
  29.                  for="citynameId">City</label>    
  30.           <div class="col-md-7">    
  31.             <input class="form-control"    
  32.                    id="citynameid"    
  33.                    type="text"    
  34.                    placeholder="Cityname (required)"    
  35.                    formControlName="cityname"    
  36.                    [ngClass]="{'is-invalid': displayMessage.cityname}" />    
  37.             <span class="invalid-feedback">    
  38.               {{displayMessage.cityname}}    
  39.             </span>    
  40.           </div>    
  41.         </div>    
  42.       
  43.         <div class="form-group row mb-2">    
  44.           <label class="col-md-3 col-form-label"    
  45.                  for="addressId">Address</label>    
  46.           <div class="col-md-7">    
  47.             <input class="form-control"    
  48.                    id="addressId"    
  49.                    type="text"    
  50.                    placeholder="Address"    
  51.                    formControlName="address" />    
  52.           </div>    
  53.         </div>    
  54.       
  55.         <div class="form-group row mb-2">    
  56.           <label class="col-md-3 col-form-label"    
  57.                  for="genderId">Gender</label>    
  58.           <div class="col-md-7">    
  59.             <select id="genderId" formControlName="gender" class="form-control">    
  60.               <option value="" disabled selected>Select an Option</option>    
  61.               <option value="Male">Male</option>    
  62.               <option value="Female">Female</option>    
  63.             </select>    
  64.       
  65.           </div>    
  66.         </div>    
  67.       
  68.         <div class="form-group row mb-2">    
  69.           <label class="col-md-3 col-form-label"    
  70.                  for="companyId">Company</label>    
  71.           <div class="col-md-7">    
  72.             <input class="form-control"    
  73.                    id="companyId"    
  74.                    type="text"    
  75.                    placeholder="Company"    
  76.                    formControlName="company" />    
  77.           </div>    
  78.         </div>    
  79.       
  80.         <div class="form-group row mb-2">    
  81.           <label class="col-md-3 col-form-label"    
  82.                  for="designationId">Designation</label>    
  83.           <div class="col-md-7">    
  84.             <input class="form-control"    
  85.                    id="designationId"    
  86.                    type="text"    
  87.                    placeholder="Designation"    
  88.                    formControlName="designation" />    
  89.           </div>    
  90.         </div>    
  91.       
  92.         <div class="form-group row mb-2">    
  93.           <div class="offset-md-2 col-md-6">    
  94.             <button class="btn btn-primary mr-3"    
  95.                     style="width:80px;"    
  96.                     type="submit"    
  97.                     [title]="employeeForm.valid ? 'Save your entered data' : 'Disabled until the form data is valid'"    
  98.                     [disabled]="!employeeForm.valid">    
  99.               Save    
  100.             </button>    
  101.             <button class="btn btn-outline-secondary mr-3"    
  102.                     style="width:80px;"    
  103.                     type="button"    
  104.                     title="Cancel your edits"    
  105.                     [routerLink]="['/employees']">    
  106.               Cancel    
  107.             </button>    
  108.             <button class="btn btn-outline-warning" *ngIf="pageTitle != 'Add Employee'"    
  109.                     style="width:80px"    
  110.                     type="button"    
  111.                     title="Delete this product"    
  112.                     (click)="deleteEmployee()">    
  113.               Delete    
  114.             </button>    
  115.           </div>    
  116.         </div>    
  117.       </form>    
  118.     </div>    
  119.       
  120.     <div class="alert alert-danger"    
  121.          *ngIf="errorMessage">{{errorMessage}}    
  122.     </div>    
  123.   </div>   
We can create an Angular guard to protect accidental data loss while editing the employee data. Use below command to create a guard.
 
ng g guard employee\EmployeeEdit
 
Modify the class with below code.
 
employee-edit.guard.ts
  1. import { Injectable } from '@angular/core';    
  2. import { CanDeactivate } from '@angular/router';    
  3. import { Observable } from 'rxjs';    
  4. import { EmployeeEditComponent } from './employee-edit/employee-edit.component';  
  5.     
  6.     
  7. @Injectable({    
  8.   providedIn: 'root'    
  9. })    
  10. export class EmployeeEditGuard implements CanDeactivate<EmployeeEditComponent> {    
  11.   canDeactivate(component: EmployeeEditComponent): Observable<boolean> | Promise<boolean> | boolean {    
  12.     if (component.employeeForm.dirty) {    
  13.       const name = component.employeeForm.get('name').value || 'New Employee';    
  14.       return confirm(`Navigate away and lose all changes to ${name}?`);    
  15.     }    
  16.     return true;    
  17.   }    
  18. }     
We can create the final component “EmployeeDetail” to show the employee detail.
 
ng g component employee\EmployeeDetail
 
Modify the component file with below code.
 
employee-detail.component.ts
  1. import { Component, OnInit } from '@angular/core';    
  2. import { ActivatedRoute, Router } from '@angular/router';  
  3. import { Employee } from '../employee';  
  4. import { EmployeeService } from '../employee.service';  
  5.     
  6. @Component({    
  7.   selector: 'app-employee-detail',    
  8.   templateUrl: './employee-detail.component.html',    
  9.   styleUrls: ['./employee-detail.component.css']    
  10. })    
  11. export class EmployeeDetailComponent implements OnInit {    
  12.   pageTitle = 'Employee Detail';    
  13.   errorMessage = '';    
  14.   employee: Employee | undefined;    
  15.     
  16.   constructor(private route: ActivatedRoute,    
  17.     private router: Router,    
  18.     private employeeService: EmployeeService) { }    
  19.     
  20.   ngOnInit() {    
  21.     const id = this.route.snapshot.paramMap.get('id');    
  22.     const cityname = this.route.snapshot.paramMap.get('cityname');    
  23.     if (id && cityname) {    
  24.       this.getEmployee(id, cityname);    
  25.     }    
  26.   }    
  27.     
  28.   getEmployee(id: string, cityName: string) {    
  29.     this.employeeService.getEmployee(id, cityName).subscribe(    
  30.       employee => this.employee = employee,    
  31.       error => this.errorMessage = <any>error);    
  32.   }    
  33.     
  34.   onBack(): void {    
  35.     this.router.navigate(['/employees']);    
  36.   }    
  37. }    
Also modify the html template file with below code.
 
employee-detail.component.html
  1. <div class="card">    
  2.     <div class="card-header"    
  3.          *ngIf="employee">    
  4.       {{pageTitle + ": " + employee.name}}    
  5.     </div>    
  6.     <div class="card-body"    
  7.          *ngIf="employee">    
  8.       <div class="row">    
  9.         <div class="col-md-8">    
  10.           <div class="row">    
  11.             <div class="col-md-3">Name:</div>    
  12.             <div class="col-md-6">{{employee.name}}</div>    
  13.           </div>    
  14.           <div class="row">    
  15.             <div class="col-md-3">City:</div>    
  16.             <div class="col-md-6">{{employee.cityname}}</div>    
  17.           </div>    
  18.           <div class="row">    
  19.             <div class="col-md-3">Address:</div>    
  20.             <div class="col-md-6">{{employee.address}}</div>    
  21.           </div>    
  22.           <div class="row">    
  23.             <div class="col-md-3">Gender:</div>    
  24.             <div class="col-md-6">{{employee.gender}}</div>    
  25.           </div>    
  26.           <div class="row">    
  27.             <div class="col-md-3">Company:</div>    
  28.             <div class="col-md-6">{{employee.company}}</div>    
  29.           </div>    
  30.           <div class="row">    
  31.             <div class="col-md-3">Designation:</div>    
  32.             <div class="col-md-6">{{employee.designation}}</div>    
  33.           </div>    
  34.         </div>    
  35.       </div>    
  36.       <div class="row mt-4">    
  37.         <div class="col-md-4">    
  38.           <button class="btn btn-outline-secondary mr-3"    
  39.                   style="width:80px"    
  40.                   (click)="onBack()">    
  41.             <i class="fa fa-chevron-left"></i> Back    
  42.           </button>    
  43.           <button class="btn btn-outline-primary"    
  44.                   style="width:80px"    
  45.                   [routerLink]="['/employees', employee.id, employee.cityname, 'edit']">    
  46.             Edit    
  47.           </button>    
  48.         </div>    
  49.       </div>    
  50.     </div>    
  51.     <div class="alert alert-danger"    
  52.          *ngIf="errorMessage">    
  53.       {{errorMessage}}    
  54.     </div>    
  55.   </div>   
Since, we are using Angular reactive forms in this application, we must import “ReactiveFormsModule” and “FormsModule” in the app.module.ts file. Also import the “HttpClientModule” for using http client service.
 
Please include the “EmployeeService” in the providers list also.
 
app.module.ts
  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3.   
  4. import { AppRoutingModule } from './app-routing.module';  
  5. import { AppComponent } from './app.component';  
  6. import { HomeComponent } from './home/home.component';  
  7. import { NavMenuComponent } from './nav-menu/nav-menu.component';  
  8. import { EmployeeListComponent } from './employee/employee-list/employee-list.component';  
  9. import { EmployeeEditComponent } from './employee/employee-edit/employee-edit.component';  
  10. import { EmployeeDetailComponent } from './employee/employee-detail/employee-detail.component';  
  11. import { EmployeeService } from './employee/employee.service';  
  12.   
  13. import { ReactiveFormsModule, FormsModule } from '@angular/forms';  
  14. import { HttpClientModule } from '@angular/common/http';  
  15.   
  16. @NgModule({  
  17.   declarations: [  
  18.     AppComponent,  
  19.     HomeComponent,  
  20.     NavMenuComponent,  
  21.     EmployeeListComponent,  
  22.     EmployeeEditComponent,  
  23.     EmployeeDetailComponent  
  24.   ],  
  25.   imports: [  
  26.     BrowserModule,  
  27.     AppRoutingModule,  
  28.     ReactiveFormsModule,    
  29.     FormsModule,    
  30.     HttpClientModule,    
  31.   ],  
  32.   providers: [  
  33.     EmployeeService  
  34.   ],  
  35.   bootstrap: [AppComponent]  
  36. })  
  37. export class AppModule { }  
We can modify the routing module with below code also. So that, all routing will work as expected.
 
app-routing.module.ts
  1. import { NgModule } from '@angular/core';  
  2. import { Routes, RouterModule } from '@angular/router';  
  3. import { HomeComponent } from './home/home.component';  
  4. import { EmployeeListComponent } from './employee/employee-list/employee-list.component';  
  5. import { EmployeeEditGuard } from './employee/employee-edit.guard';  
  6. import { EmployeeDetailComponent } from './employee/employee-detail/employee-detail.component';  
  7. import { EmployeeEditComponent } from './employee/employee-edit/employee-edit.component';  
  8.   
  9. const routes: Routes = [  
  10.   { path: '', component: HomeComponent, pathMatch: 'full' },  
  11.   {  
  12.     path: 'employees',  
  13.     component: EmployeeListComponent  
  14.   },  
  15.   {  
  16.     path: 'employees/:id/:cityname',  
  17.     component: EmployeeDetailComponent  
  18.   },  
  19.   {  
  20.     path: 'employees/:id/:cityname/edit',  
  21.     canDeactivate: [EmployeeEditGuard],  
  22.     component: EmployeeEditComponent  
  23.   },  
  24. ]  
  25.   
  26. @NgModule({  
  27.   imports: [RouterModule.forRoot(routes)],  
  28.   exports: [RouterModule]  
  29. })  
  30. export class AppRoutingModule { }  
We can modify the app component html template file with below code.
 
app.component.html
  1. <body>  
  2.   <app-nav-menu></app-nav-menu>  
  3.   <div class="container">  
  4.     <router-outlet></router-outlet>  
  5.   </div>  
  6. </body>  
We have completed the Angular side coding as well. We can run both ASP.NET core web api project along with Angular project. Please make sure that, Cosmos DB emulator is also working properly.
 
 
 
We can click the Employee tab and add a new employee record.  
 
 
 
If you open another browser at the same time, you can see that new employee data will be shown in the employee list of that browser also.
 
 
 
I have opened the application in Chrome and Firefox browsers.
 
You can perform the edit and delete actions with our application as well.
 

Conclusion

 
In this post, we have created an ASP.NET core web API application with SignalR real-time broadcasting. We have used Cosmos DB to save and retrieve the employee information. We have broadcasted a message to all connected clients from server using SignalR hub. We have created an Angular application with all components and route guard using Angular CLI. We have successfully executed ASP.NET Core and Angular applications and created an employee record and saw the real-time data refresh in another browser also.