Real-time Angular 11 Application With SignalR And .NET 5

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 here, we will create an employee application in Angular 11 along with .NET 5 backend to describe real-time features. We will create a .NET 5 application with SQL server database to insert, edit and delete employee data. We will also create an Angular 11 application as front-end. When we add a new employee data or update or delete the data, we will get broadcasted message from SignalR hub in the Angular application and immediately show the modified data in all connected client browsers. We will also display the notification instantly, as a bell icon in the menu bar. User can click in the notification bell icon and see all the notification history. We will also provide the option to delete all notification history from database.

Create .NET 5 Web API application in Visual Studio 2019


We can create a new Web API with .NET 5 SDK in Visual Studio 2019. We will choose the ASP.NET Core Web API template. We will also choose the default “Enable Open API support” option. This feature will help us to enable swagger API documentation in our application.
 
We can create a “Models” folder and create two classes “Employee” and “Notification”.
 
Employee.cs
  1. namespace NET5SignalR.Models  
  2. {  
  3.     public class Employee  
  4.     {  
  5.         public string Id { getset; }  
  6.         public string Name { getset; }  
  7.         public string Designation { getset; }  
  8.         public string Company { getset; }  
  9.         public string Cityname { getset; }  
  10.         public string Address { getset; }  
  11.         public string Gender { getset; }  
  12.     }  
  13. }  
Notification.cs
  1. using System.ComponentModel.DataAnnotations.Schema;  
  2.   
  3. namespace NET5SignalR.Models  
  4. {  
  5.     public class Notification  
  6.     {  
  7.         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]  
  8.         public int Id { getset; }  
  9.         public string EmployeeName { getset; }  
  10.         public string TranType { getset; }  
  11.     }  
  12. }  
We can create our Employee API controller using scaffolding feature in Visual Studio. Please note that, we are using entity framework code first approach in this application.
 
 
 
We have chosen Employee model from the class list and chose a new name for Db context class.
 
Scaffolding feature will add the required NuGet packages to the application and will create an API controller with all CRUD default methods.
 
We can see that a new class “MyDbContext” is created inside the “Data” folder automatically. This class is created with DbSet property of Employee class. This will be used to create an Employee table while Db migration time. We can add one more DbSet property for Notification table.
 
MyDbContext.cs
  1. using Microsoft.EntityFrameworkCore;  
  2. using NET5SignalR.Models;  
  3.   
  4. namespace NET5SignalR.Data  
  5. {  
  6.     public class MyDbContext : DbContext  
  7.     {  
  8.         public MyDbContext (DbContextOptions<MyDbContext> options)  
  9.             : base(options)  
  10.         {  
  11.         }  
  12.   
  13.         public DbSet<Employee> Employee { getset; }  
  14.         public DbSet<Notification> Notification { getset; }  
  15.     }  
  16. }  
Scaffolding has also created a connection string in appsettings.json file for database connectivity. Now we can use “Package Manager Console” to create database and tables.
 
 
 
Above migration command will create a migration script inside the “Migrations” folder. We can use below command to create database and tables using above script.
 
 
 
If you look at the SQL server object explorer in Visual Studio, you can see that the new database is created with two tables.
 
 
 
We can install the NuGet package “Microsoft.AspNet.SignalR” now.
 
We will create an interface “IHubClient” followed by a class “BroadcastHub” inside the “Models” folder.
 
IHubClient.cs
  1. using System.Threading.Tasks;  
  2.   
  3. namespace NET5SignalR.Models  
  4. {  
  5.     public interface IHubClient  
  6.     {  
  7.         Task BroadcastMessage();  
  8.     }  
  9. }  
BroadcastHub.cs
  1. using Microsoft.AspNetCore.SignalR;  
  2.   
  3. namespace NET5SignalR.Models  
  4. {  
  5.     public class BroadcastHub : Hub<IHubClient>  
  6.     {  
  7.     }  
  8. }  
Both interface and class will be used to broadcast real-time messages to Angular application.
 
We must add below changes in the Startup class to broadcast messages from SignalR to Angular client.
 
Startup.cs
  1. using Microsoft.AspNetCore.Builder;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.EntityFrameworkCore;  
  4. using Microsoft.Extensions.Configuration;  
  5. using Microsoft.Extensions.DependencyInjection;  
  6. using Microsoft.Extensions.Hosting;  
  7. using Microsoft.OpenApi.Models;  
  8. using NET5SignalR.Data;  
  9. using NET5SignalR.Models;  
  10.   
  11. namespace NET5SignalR  
  12. {  
  13.     public class Startup  
  14.     {  
  15.         public Startup(IConfiguration configuration)  
  16.         {  
  17.             Configuration = configuration;  
  18.         }  
  19.   
  20.         public IConfiguration Configuration { get; }  
  21.   
  22.         public void ConfigureServices(IServiceCollection services)  
  23.         {  
  24.   
  25.             services.AddControllers();  
  26.             services.AddSwaggerGen(c =>  
  27.             {  
  28.                 c.SwaggerDoc("v1"new OpenApiInfo { Title = "NET5SignalR", Version = "v1" });  
  29.             });  
  30.   
  31.             services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {  
  32.                 builder  
  33.                 .AllowAnyMethod()  
  34.                 .AllowAnyHeader()  
  35.                 .AllowCredentials()  
  36.                 .WithOrigins("http://localhost:4200");  
  37.             }));  
  38.   
  39.             services.AddSignalR();  
  40.   
  41.             services.AddDbContext<MyDbContext>(options =>  
  42.                     options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));  
  43.         }  
  44.   
  45.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  46.         {  
  47.             if (env.IsDevelopment())  
  48.             {  
  49.                 app.UseDeveloperExceptionPage();  
  50.                 app.UseSwagger();  
  51.                 app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json""NET5SignalR v1"));  
  52.             }  
  53.   
  54.             app.UseRouting();  
  55.   
  56.             app.UseAuthorization();  
  57.   
  58.             app.UseCors("CorsPolicy");  
  59.   
  60.             app.UseEndpoints(endpoints =>  
  61.             {  
  62.                 endpoints.MapHub<BroadcastHub>("/notify");  
  63.             });  
  64.   
  65.             app.UseEndpoints(endpoints =>  
  66.             {  
  67.                 endpoints.MapControllers();  
  68.             });  
  69.         }  
  70.     }  
  71. }  
 
We must modify the default Employee controller with below code changes.
 
EmployeesController.cs
  1. using Microsoft.AspNetCore.Mvc;  
  2. using Microsoft.AspNetCore.SignalR;  
  3. using Microsoft.EntityFrameworkCore;  
  4. using NET5SignalR.Data;  
  5. using NET5SignalR.Models;  
  6. using System;  
  7. using System.Collections.Generic;  
  8. using System.Linq;  
  9. using System.Threading.Tasks;  
  10.   
  11. namespace NET5SignalR.Controllers  
  12. {  
  13.     [Route("api/[controller]")]  
  14.     [ApiController]  
  15.     public class EmployeesController : ControllerBase  
  16.     {  
  17.         private readonly MyDbContext _context;  
  18.         private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;  
  19.   
  20.         public EmployeesController(MyDbContext context, IHubContext<BroadcastHub, IHubClient> hubContext)  
  21.         {  
  22.             _context = context;  
  23.             _hubContext = hubContext;  
  24.         }  
  25.   
  26.         // GET: api/Employees  
  27.         [HttpGet]  
  28.         public async Task<ActionResult<IEnumerable<Employee>>> GetEmployee()  
  29.         {  
  30.             return await _context.Employee.ToListAsync();  
  31.         }  
  32.   
  33.         // GET: api/Employees/5  
  34.         [HttpGet("{id}")]  
  35.         public async Task<ActionResult<Employee>> GetEmployee(string id)  
  36.         {  
  37.             var employee = await _context.Employee.FindAsync(id);  
  38.   
  39.             if (employee == null)  
  40.             {  
  41.                 return NotFound();  
  42.             }  
  43.   
  44.             return employee;  
  45.         }  
  46.   
  47.         // PUT: api/Employees/5  
  48.         // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754  
  49.         [HttpPut("{id}")]  
  50.         public async Task<IActionResult> PutEmployee(string id, Employee employee)  
  51.         {  
  52.             if (id != employee.Id)  
  53.             {  
  54.                 return BadRequest();  
  55.             }  
  56.   
  57.             _context.Entry(employee).State = EntityState.Modified;  
  58.   
  59.             Notification notification = new Notification()  
  60.             {  
  61.                 EmployeeName = employee.Name,  
  62.                 TranType = "Edit"  
  63.             };  
  64.             _context.Notification.Add(notification);  
  65.   
  66.             try  
  67.             {  
  68.                 await _context.SaveChangesAsync();  
  69.                 await _hubContext.Clients.All.BroadcastMessage();  
  70.             }  
  71.             catch (DbUpdateConcurrencyException)  
  72.             {  
  73.                 if (!EmployeeExists(id))  
  74.                 {  
  75.                     return NotFound();  
  76.                 }  
  77.                 else  
  78.                 {  
  79.                     throw;  
  80.                 }  
  81.             }  
  82.   
  83.             return NoContent();  
  84.         }  
  85.   
  86.         // POST: api/Employees  
  87.         // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754  
  88.         [HttpPost]  
  89.         public async Task<ActionResult<Employee>> PostEmployee(Employee employee)  
  90.         {  
  91.             employee.Id = Guid.NewGuid().ToString();  
  92.             _context.Employee.Add(employee);  
  93.   
  94.             Notification notification = new Notification()  
  95.             {  
  96.                 EmployeeName = employee.Name,  
  97.                 TranType = "Add"  
  98.             };  
  99.             _context.Notification.Add(notification);  
  100.   
  101.             try  
  102.             {  
  103.                 await _context.SaveChangesAsync();  
  104.                 await _hubContext.Clients.All.BroadcastMessage();  
  105.             }  
  106.             catch (DbUpdateException)  
  107.             {  
  108.                 if (EmployeeExists(employee.Id))  
  109.                 {  
  110.                     return Conflict();  
  111.                 }  
  112.                 else  
  113.                 {  
  114.                     throw;  
  115.                 }  
  116.             }  
  117.   
  118.             return CreatedAtAction("GetEmployee"new { id = employee.Id }, employee);  
  119.         }  
  120.   
  121.         // DELETE: api/Employees/5  
  122.         [HttpDelete("{id}")]  
  123.         public async Task<IActionResult> DeleteEmployee(string id)  
  124.         {  
  125.             var employee = await _context.Employee.FindAsync(id);  
  126.             if (employee == null)  
  127.             {  
  128.                 return NotFound();  
  129.             }  
  130.   
  131.             Notification notification = new Notification()  
  132.             {  
  133.                 EmployeeName = employee.Name,  
  134.                 TranType = "Delete"  
  135.             };  
  136.   
  137.             _context.Employee.Remove(employee);  
  138.             _context.Notification.Add(notification);  
  139.   
  140.             await _context.SaveChangesAsync();  
  141.             await _hubContext.Clients.All.BroadcastMessage();  
  142.   
  143.             return NoContent();  
  144.         }  
  145.   
  146.         private bool EmployeeExists(string id)  
  147.         {  
  148.             return _context.Employee.Any(e => e.Id == id);  
  149.         }  
  150.     }  
  151. }  
We will be adding a new record to Notification table after adding, editing or deleting a record in the Employee table inside the respective web methods. Also notice that, we have broadcasted the message to all connected clients inside these web methods.
 
We must create below models for notification count and notification result.
 
NotificationCountResult.cs
  1. namespace NET5SignalR.Models  
  2. {  
  3.     public class NotificationCountResult  
  4.     {  
  5.         public int Count { getset; }  
  6.     }  
  7. }  
NotificationResult.cs
  1. namespace NET5SignalR.Models  
  2. {  
  3.     public class NotificationResult  
  4.     {  
  5.         public string EmployeeName { getset; }  
  6.         public string TranType { getset; }  
  7.     }  
  8. }  
We can create new API class “NotificationsController” to get details from Notification table. This controller will also use to delete entire records from Notification table.
 
NotificationsController.cs
  1. using Microsoft.AspNetCore.Mvc;  
  2. using Microsoft.AspNetCore.SignalR;  
  3. using Microsoft.EntityFrameworkCore;  
  4. using NET5SignalR.Data;  
  5. using NET5SignalR.Models;  
  6. using System.Collections.Generic;  
  7. using System.Linq;  
  8. using System.Threading.Tasks;  
  9.   
  10. namespace NET5SignalR.Controllers  
  11. {  
  12.     [Route("api/[controller]")]  
  13.     [ApiController]  
  14.     public class NotificationsController : ControllerBase  
  15.     {  
  16.         private readonly MyDbContext _context;  
  17.         private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;  
  18.   
  19.         public NotificationsController(MyDbContext context, IHubContext<BroadcastHub, IHubClient> hubContext)  
  20.         {  
  21.             _context = context;  
  22.             _hubContext = hubContext;  
  23.         }  
  24.   
  25.         // GET: api/Notifications/notificationcount  
  26.         [Route("notificationcount")]  
  27.         [HttpGet]  
  28.         public async Task<ActionResult<NotificationCountResult>> GetNotificationCount()  
  29.         {  
  30.             var count = (from not in _context.Notification  
  31.                          select not).CountAsync();  
  32.             NotificationCountResult result = new NotificationCountResult  
  33.             {  
  34.                 Count = await count  
  35.             };  
  36.             return result;  
  37.         }  
  38.   
  39.         // GET: api/Notifications/notificationresult  
  40.         [Route("notificationresult")]  
  41.         [HttpGet]  
  42.         public async Task<ActionResult<List<NotificationResult>>> GetNotificationMessage()  
  43.         {  
  44.             var results = from message in _context.Notification  
  45.                         orderby message.Id descending  
  46.                         select new NotificationResult  
  47.                         {  
  48.                             EmployeeName = message.EmployeeName,  
  49.                             TranType = message.TranType  
  50.                         };  
  51.             return await results.ToListAsync();  
  52.         }  
  53.   
  54.         // DELETE: api/Notifications/deletenotifications  
  55.         [HttpDelete]  
  56.         [Route("deletenotifications")]  
  57.         public async Task<IActionResult> DeleteNotifications()  
  58.         {  
  59.             await _context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE Notification");  
  60.             await _context.SaveChangesAsync();  
  61.             await _hubContext.Clients.All.BroadcastMessage();  
  62.   
  63.             return NoContent();  
  64.         }  
  65.     }  
  66. }  
Above controller has three web methods. “GetNotificationCount” is used to get total notification count and “GetNotificationMessage” is used to get all notification details (Employee name and Transaction type). “DeleteNotifications” is used to delete entire records from Notification table.
 
We have completed all API side code. If needed, you can check all the web methods using Swagger or Postman tool.

Create Angular 11 application using CLI


We can create the Angular 11 application using Angular CLI. We will create all the services and components step by step.
 
Create a new Angular application using below command.
 
ng new AngularSignalR
 
We can choose the option to create Routing. (Be default, it is false)
 
It will take some time to install all the node packages. We can install below three packages using npm command.
 
npm install @microsoft/signalr
npm install bootstrap
npm install font-awesome
 
We have now installed the SignalR client, bootstrap and font-awesome packages in our Angular 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";  
Create an environment variable inside environment class for baseUrl. This will be used across the application.
 
environment.ts
  1. // This file can be replaced during build by using the `fileReplacements` array.  
  2. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.  
  3. // The list of file replacements can be found in `angular.json`.  
  4.   
  5. export const environment = {  
  6.   production: false,  
  7.   baseUrl: 'http://localhost:62769/'  
  8. };  
  9.   
  10. /* 
  11.  * For easier debugging in development mode, you can import the following file 
  12.  * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 
  13.  * 
  14.  * This import should be commented out in production mode because it will have a negative impact 
  15.  * on performance if an error is thrown. 
  16.  */  
  17. // import 'zone.js/dist/zone-error';  // Included with Angular CLI.  
Please replace the API end point with your API end point port number.
 
We can create an employee class now.
 
ng g class employee\employee
 
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 an employee service now.
 
ng g service employee\employee
 
employee.service.ts
  1. import { Injectable } 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. import { environment } from 'src/environments/environment';  
  7.   
  8. @Injectable({  
  9.   providedIn: 'root'  
  10. })  
  11. export class EmployeeService {  
  12.   private employeesUrl = environment.baseUrl + 'api/employees';  
  13.   
  14.   constructor(private http: HttpClient) { }  
  15.   
  16.   getEmployees(): Observable<Employee[]> {  
  17.     return this.http.get<Employee[]>(this.employeesUrl)  
  18.       .pipe(  
  19.         catchError(this.handleError)  
  20.       );  
  21.   }  
  22.   
  23.   getEmployee(id: string): Observable<Employee> {  
  24.     if (id === '') {  
  25.       return of(this.initializeEmployee());  
  26.     }  
  27.     const url = `${this.employeesUrl}/${id}`;  
  28.     return this.http.get<Employee>(url)  
  29.       .pipe(  
  30.         catchError(this.handleError)  
  31.       );  
  32.   }  
  33.   
  34.   createEmployee(employee: Employee): Observable<Employee> {  
  35.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  36.     return this.http.post<Employee>(this.employeesUrl, employee, { headers: headers })  
  37.       .pipe(  
  38.         catchError(this.handleError)  
  39.       );  
  40.   }  
  41.   
  42.   deleteEmployee(id: string): Observable<{}> {  
  43.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  44.     const url = `${this.employeesUrl}/${id}`;  
  45.     return this.http.delete<Employee>(url, { headers: headers })  
  46.       .pipe(  
  47.         catchError(this.handleError)  
  48.       );  
  49.   }  
  50.   
  51.   updateEmployee(employee: Employee): Observable<Employee> {  
  52.     debugger  
  53.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  54.     const url = `${this.employeesUrl}/${employee.id}`;  
  55.     return this.http.put<Employee>(url, employee, { headers: headers })  
  56.       .pipe(  
  57.         map(() => employee),  
  58.         catchError(this.handleError)  
  59.       );  
  60.   }  
  61.   
  62.   private handleError(err) {  
  63.     let errorMessage: string;  
  64.     if (err.error instanceof ErrorEvent) {  
  65.       errorMessage = `An error occurred: ${err.error.message}`;  
  66.     } else {  
  67.       errorMessage = `Backend returned code ${err.status}: ${err.body.error}`;  
  68.     }  
  69.     console.error(err);  
  70.     return throwError(errorMessage);  
  71.   }  
  72.   
  73.   private initializeEmployee(): Employee {  
  74.     return {  
  75.       id: null,  
  76.       name: null,  
  77.       address: null,  
  78.       gender: null,  
  79.       company: null,  
  80.       designation: null,  
  81.       cityname: null  
  82.     };  
  83.   }  
  84. }    
We can create employee list component. This component will be used to display all the employee information. This component also uses to edit and delete employee data.
 
ng g component employee\EmployeeList
 
We can modify the class 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 '@microsoft/signalr';  
  5. import { environment } from 'src/environments/environment';  
  6.   
  7. @Component({  
  8.   selector: 'app-employee-list',  
  9.   templateUrl: './employee-list.component.html',  
  10.   styleUrls: ['./employee-list.component.css']  
  11. })  
  12. export class EmployeeListComponent implements OnInit {  
  13.   pageTitle = 'Employee List';  
  14.   filteredEmployees: Employee[] = [];  
  15.   employees: Employee[] = [];  
  16.   errorMessage = '';  
  17.   
  18.   _listFilter = '';  
  19.   get listFilter(): string {  
  20.     return this._listFilter;  
  21.   }  
  22.   set listFilter(value: string) {  
  23.     this._listFilter = value;  
  24.     this.filteredEmployees = this.listFilter ? this.performFilter(this.listFilter) : this.employees;  
  25.   }  
  26.   
  27.   constructor(private employeeService: EmployeeService) { }  
  28.   
  29.   performFilter(filterBy: string): Employee[] {  
  30.     filterBy = filterBy.toLocaleLowerCase();  
  31.     return this.employees.filter((employee: Employee) =>  
  32.       employee.name.toLocaleLowerCase().indexOf(filterBy) !== -1);  
  33.   }  
  34.   
  35.   ngOnInit(): void {  
  36.     this.getEmployeeData();  
  37.   
  38.     const connection = new signalR.HubConnectionBuilder()  
  39.       .configureLogging(signalR.LogLevel.Information)  
  40.       .withUrl(environment.baseUrl + 'notify')  
  41.       .build();  
  42.   
  43.     connection.start().then(function () {  
  44.       console.log('SignalR Connected!');  
  45.     }).catch(function (err) {  
  46.       return console.error(err.toString());  
  47.     });  
  48.   
  49.     connection.on("BroadcastMessage", () => {  
  50.       this.getEmployeeData();  
  51.     });  
  52.   }  
  53.   
  54.   getEmployeeData() {  
  55.     this.employeeService.getEmployees().subscribe(  
  56.       employees => {  
  57.         this.employees = employees;  
  58.         this.filteredEmployees = this.employees;  
  59.       },  
  60.       error => this.errorMessage = <any>error  
  61.     );  
  62.   }  
  63.   
  64.   deleteEmployee(id: string, name: string): void {  
  65.     if (id === '') {  
  66.       this.onSaveComplete();  
  67.     } else {  
  68.       if (confirm(`Are you sure want to delete this Employee: ${name}?`)) {  
  69.         this.employeeService.deleteEmployee(id)  
  70.           .subscribe(  
  71.             () => this.onSaveComplete(),  
  72.             (error: any) => this.errorMessage = <any>error  
  73.           );  
  74.       }  
  75.     }  
  76.   }  
  77.   
  78.   onSaveComplete(): void {  
  79.     this.employeeService.getEmployees().subscribe(  
  80.       employees => {  
  81.         this.employees = employees;  
  82.         this.filteredEmployees = this.employees;  
  83.       },  
  84.       error => this.errorMessage = <any>error  
  85.     );  
  86.   }  
  87.   
  88. }    
If you look at the code, you can see that inside ngOnInit method, I have created a constant variable with signalR hub connection builder and also started the connection. This connection will be listening to the messages from SignlarR hub from backend Web API. Whenever, backend sends a message, getEmployeeData method will be triggered automatically.
 
We can modify the template and style files also.
 
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/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]">  
  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, '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);">  
  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>  
employee-list.component.css
  1. thead {  
  2.     color#337AB7;  
  3. }  
We can create employee edit component with below command
 
ng g component employee\EmployeeEdit
 
Modify the class file with below code.
 
employee-edit.component.ts
  1. import { Component, OnInit, 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 { Employee } from '../employee';  
  6. import { EmployeeService } from '../employee.service';  
  7.   
  8. @Component({  
  9.   selector: 'app-employee-edit',  
  10.   templateUrl: './employee-edit.component.html',  
  11.   styleUrls: ['./employee-edit.component.css']  
  12. })  
  13. export class EmployeeEditComponent implements OnInit, OnDestroy {  
  14.   @ViewChildren(FormControlName, { read: ElementRef }) formInputElements: ElementRef[];  
  15.   pageTitle = 'Employee Edit';  
  16.   errorMessage: string;  
  17.   employeeForm: FormGroup;  
  18.   tranMode: string;  
  19.   employee: Employee;  
  20.   private sub: Subscription;  
  21.   
  22.   displayMessage: { [key: string]: string } = {};  
  23.   private validationMessages: { [key: string]: { [key: string]: string } };  
  24.   
  25.   constructor(private fb: FormBuilder,  
  26.     private route: ActivatedRoute,  
  27.     private router: Router,  
  28.     private employeeService: EmployeeService) {  
  29.   
  30.     this.validationMessages = {  
  31.       name: {  
  32.         required: 'Employee name is required.',  
  33.         minlength: 'Employee name must be at least three characters.',  
  34.         maxlength: 'Employee name cannot exceed 50 characters.'  
  35.       },  
  36.       cityname: {  
  37.         required: 'Employee city name is required.',  
  38.       }  
  39.     };  
  40.   }  
  41.   
  42.   ngOnInit() {  
  43.     this.tranMode = "new";  
  44.     this.employeeForm = this.fb.group({  
  45.       name: ['', [Validators.required,  
  46.       Validators.minLength(3),  
  47.       Validators.maxLength(50)  
  48.       ]],  
  49.       address: '',  
  50.       cityname: ['', [Validators.required]],  
  51.       gender: '',  
  52.       company: '',  
  53.       designation: '',  
  54.     });  
  55.   
  56.     this.sub = this.route.paramMap.subscribe(  
  57.       params => {  
  58.         const id = params.get('id');  
  59.         const cityname = params.get('cityname');  
  60.         if (id == '0') {  
  61.           const employee: Employee = { id: "0", name: "", address: "", gender: "", company: "", designation: "", cityname: "" };  
  62.           this.displayEmployee(employee);  
  63.         }  
  64.         else {  
  65.           this.getEmployee(id);  
  66.         }  
  67.       }  
  68.     );  
  69.   }  
  70.   
  71.   ngOnDestroy(): void {  
  72.     this.sub.unsubscribe();  
  73.   }  
  74.   
  75.   getEmployee(id: string): void {  
  76.     this.employeeService.getEmployee(id)  
  77.       .subscribe(  
  78.         (employee: Employee) => this.displayEmployee(employee),  
  79.         (error: any) => this.errorMessage = <any>error  
  80.       );  
  81.   }  
  82.   
  83.   displayEmployee(employee: Employee): void {  
  84.     if (this.employeeForm) {  
  85.       this.employeeForm.reset();  
  86.     }  
  87.     this.employee = employee;  
  88.     if (this.employee.id == '0') {  
  89.       this.pageTitle = 'Add Employee';  
  90.     } else {  
  91.       this.pageTitle = `Edit Employee: ${this.employee.name}`;  
  92.     }  
  93.     this.employeeForm.patchValue({  
  94.       name: this.employee.name,  
  95.       address: this.employee.address,  
  96.       gender: this.employee.gender,  
  97.       company: this.employee.company,  
  98.       designation: this.employee.designation,  
  99.       cityname: this.employee.cityname  
  100.     });  
  101.   }  
  102.   
  103.   deleteEmployee(): void {  
  104.     if (this.employee.id == '0') {  
  105.       this.onSaveComplete();  
  106.     } else {  
  107.       if (confirm(`Are you sure want to delete this Employee: ${this.employee.name}?`)) {  
  108.         this.employeeService.deleteEmployee(this.employee.id)  
  109.           .subscribe(  
  110.             () => this.onSaveComplete(),  
  111.             (error: any) => this.errorMessage = <any>error  
  112.           );  
  113.       }  
  114.     }  
  115.   }  
  116.   
  117.   saveEmployee(): void {  
  118.     if (this.employeeForm.valid) {  
  119.       if (this.employeeForm.dirty) {  
  120.         const p = { ...this.employee, ...this.employeeForm.value };  
  121.         if (p.id === '0') {  
  122.           this.employeeService.createEmployee(p)  
  123.             .subscribe(  
  124.               () => this.onSaveComplete(),  
  125.               (error: any) => this.errorMessage = <any>error  
  126.             );  
  127.         } else {  
  128.           this.employeeService.updateEmployee(p)  
  129.             .subscribe(  
  130.               () => this.onSaveComplete(),  
  131.               (error: any) => this.errorMessage = <any>error  
  132.             );  
  133.         }  
  134.       } else {  
  135.         this.onSaveComplete();  
  136.       }  
  137.     } else {  
  138.       this.errorMessage = 'Please correct the validation errors.';  
  139.     }  
  140.   }  
  141.   
  142.   onSaveComplete(): void {  
  143.     this.employeeForm.reset();  
  144.     this.router.navigate(['/employees']);  
  145.   }  
  146. }    
We can modify the template file also.
 
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 need one more component to display the employee details in a separate window. We can create now.
 
ng g component employee\EmployeeDetail
 
We can modify the class 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.     if (id) {    
  23.       this.getEmployee(id);    
  24.     }    
  25.   }    
  26.     
  27.   getEmployee(id: string) {    
  28.     this.employeeService.getEmployee(id).subscribe(    
  29.       employee => this.employee = employee,    
  30.       error => this.errorMessage = <any>error);    
  31.   }    
  32.     
  33.   onBack(): void {    
  34.     this.router.navigate(['/employees']);    
  35.   }    
  36. }    
Modify the 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,'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>   
We need a modal popup window to display the notification messages. As I mentioned earlier, application will create new record into notification table for each transaction like Add/Edit/Delete.
 
We can create modal service first.
 
ng g service modal\modal
 
Modify the service class with below code.
 
modal.service.ts
  1. import { Injectable } from '@angular/core';  
  2.   
  3. @Injectable({  
  4.   providedIn: 'root'  
  5. })  
  6. export class ModalService {  
  7.   
  8.   constructor() { }  
  9.   
  10.   private modals: any[] = [];  
  11.   
  12.     add(modal: any) {  
  13.         this.modals.push(modal);  
  14.     }  
  15.   
  16.     remove(id: string) {  
  17.         this.modals = this.modals.filter(x => x.id !== id);  
  18.     }  
  19.   
  20.     open(id: string) {  
  21.         const modal = this.modals.find(x => x.id === id);  
  22.         modal.open();  
  23.     }  
  24.   
  25.     close(id: string) {  
  26.         const modal = this.modals.find(x => x.id === id);  
  27.         modal.close();  
  28.     }  
  29. }  
Now, we can create the component using below command.
 
ng g component modal
 
Modify the class file with below code.
 
modal.component.ts
  1. import { Component, ElementRef, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';  
  2. import { ModalService } from './modal.service';  
  3.   
  4. @Component({  
  5.   selector: 'app-modal',  
  6.   templateUrl: './modal.component.html',  
  7.   styleUrls: ['./modal.component.less'],  
  8.   encapsulation: ViewEncapsulation.None  
  9. })  
  10. export class ModalComponent implements OnInit, OnDestroy {  
  11.   
  12.   @Input() id: string;  
  13.   private element: any;  
  14.   
  15.   constructor(private modalService: ModalService, private el: ElementRef) {  
  16.     this.element = el.nativeElement;  
  17.   }  
  18.   
  19.   ngOnInit() {  
  20.     if (!this.id) {  
  21.       console.error('modal must have an id');  
  22.       return;  
  23.     }  
  24.   
  25.     document.body.appendChild(this.element);  
  26.   
  27.     this.element.addEventListener('click', el => {  
  28.       if (el.target.className === 'app-modal') {  
  29.         this.close();  
  30.       }  
  31.     });  
  32.   
  33.     this.modalService.add(this);  
  34.   }  
  35.   
  36.   ngOnDestroy(): void {  
  37.     this.modalService.remove(this.id);  
  38.     this.element.remove();  
  39.   }  
  40.   
  41.   open(): void {  
  42.     this.element.style.display = 'block';  
  43.     document.body.classList.add('app-modal-open');  
  44.   }  
  45.   
  46.   close(): void {  
  47.     this.element.style.display = 'none';  
  48.     document.body.classList.remove('app-modal-open');  
  49.   }  
  50. }  
 
Please note that, we are using the “less” stylesheet instead of default “css” for this component.
 
modal.component.less
  1. app-modal {  
  2.     displaynone;  
  3.   
  4.     .app-modal {  
  5.         positionfixed;  
  6.         top: 1%;  
  7.         right: 0;  
  8.         bottom: 0;  
  9.         left: 25%;  
  10.   
  11.         z-index1000;  
  12.           
  13.         overflowauto;  
  14.   
  15.         .app-modal-body {  
  16.             padding10px;  
  17.             background#fff;  
  18.   
  19.             overflow-y: auto;  
  20.             margin-top40px;  
  21.             width700px;  
  22.             height450px;  
  23.         }  
  24.     }  
  25.   
  26.     .app-modal-background {  
  27.         positionfixed;  
  28.         top: 0;  
  29.         right: 0;  
  30.         bottom: 0;  
  31.         left: 0;  
  32.   
  33.         background-color#000;  
  34.         opacity: 0.75;  
  35.           
  36.         z-index900;  
  37.     }  
  38. }  
  39.   
  40. body.app-modal-open {  
  41.     overflowhidden;  
  42. }  
“less” stylesheet files allow us the flexibility to add some logical coding inside the stylesheet.
 
We can modify the html template with below code.
 
modal.component.html
  1. <div class="app-modal">  
  2.     <div class="app-modal-body">  
  3.         <ng-content></ng-content>  
  4.     </div>  
  5. </div>  
  6. <div class="app-modal-background"></div>  
We can create a notification class file and add two models “NotificationCountResult” and “NotificationResult” inside it.
 
ng g class notification\notification
 
notification.ts
  1. export class NotificationCountResult {  
  2.     count: number;  
  3. }  
  4.   
  5. export class NotificationResult {  
  6.     employeeName: string;  
  7.     tranType: string;  
  8. }  
We can create the notification service now.
 
ng g service notification\notification
 
Replace service with below code.
 
notification.service.ts
  1. import { HttpClient, HttpHeaders } from '@angular/common/http';  
  2. import { Injectable } from '@angular/core';  
  3. import { Observable, throwError } from 'rxjs';  
  4. import { catchError } from 'rxjs/operators';  
  5. import { environment } from 'src/environments/environment';  
  6. import { NotificationCountResult, NotificationResult } from './notification';  
  7.   
  8. @Injectable({  
  9.   providedIn: 'root'  
  10. })  
  11. export class NotificationService {  
  12.   
  13.   private notificationsUrl = environment.baseUrl +'api/notifications';  
  14.   
  15.   constructor(private http: HttpClient) { }  
  16.   
  17.   getNotificationCount(): Observable<NotificationCountResult> {  
  18.     const url = `${this.notificationsUrl}/notificationcount`;  
  19.     return this.http.get<NotificationCountResult>(url)  
  20.       .pipe(  
  21.         catchError(this.handleError)  
  22.       );  
  23.   }  
  24.   
  25.   getNotificationMessage(): Observable<Array<NotificationResult>> {  
  26.     const url = `${this.notificationsUrl}/notificationresult`;  
  27.     return this.http.get<Array<NotificationResult>>(url)  
  28.       .pipe(  
  29.         catchError(this.handleError)  
  30.       );  
  31.   }  
  32.   
  33.   deleteNotifications(): Observable<{}> {  
  34.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  35.     const url = `${this.notificationsUrl}/deletenotifications`;  
  36.     return this.http.delete(url, { headers: headers })  
  37.       .pipe(  
  38.         catchError(this.handleError)  
  39.       );  
  40.   }  
  41.   
  42.   private handleError(err) {  
  43.     let errorMessage: string;  
  44.     if (err.error instanceof ErrorEvent) {  
  45.       errorMessage = `An error occurred: ${err.error.message}`;  
  46.     } else {  
  47.       errorMessage = `Backend returned code ${err.status}: ${err.body.error}`;  
  48.     }  
  49.     console.error(err);  
  50.     return throwError(errorMessage);  
  51.   }  
  52. }  
Create the navigation menu component now.
 
ng g component NavMenu
 
Modify the class file with below code.
 
nav-menu.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2. import { ModalService } from '../modal/modal.service';  
  3. import * as signalR from '@microsoft/signalr';  
  4. import { NotificationCountResult, NotificationResult } from '../Notification/notification';  
  5. import { NotificationService } from '../Notification/notification.service';  
  6. import { environment } from 'src/environments/environment';  
  7.   
  8. @Component({  
  9.   selector: 'app-nav-menu',  
  10.   templateUrl: './nav-menu.component.html',  
  11.   styleUrls: ['./nav-menu.component.css']  
  12. })  
  13. export class NavMenuComponent implements OnInit {  
  14.   
  15.   notification: NotificationCountResult;  
  16.   messages: Array<NotificationResult>;  
  17.   errorMessage = '';  
  18.   
  19.   constructor(private notificationService: NotificationService, private modalService: ModalService) { }  
  20.   isExpanded = false;  
  21.   
  22.   ngOnInit() {  
  23.     this.getNotificationCount();  
  24.     const connection = new signalR.HubConnectionBuilder()  
  25.       .configureLogging(signalR.LogLevel.Information)  
  26.       .withUrl(environment.baseUrl + 'notify')  
  27.       .build();  
  28.   
  29.     connection.start().then(function () {  
  30.       console.log('SignalR Connected!');  
  31.     }).catch(function (err) {  
  32.       return console.error(err.toString());  
  33.     });  
  34.   
  35.     connection.on("BroadcastMessage", () => {  
  36.       this.getNotificationCount();  
  37.     });  
  38.   }  
  39.   
  40.   collapse() {  
  41.     this.isExpanded = false;  
  42.   }  
  43.   
  44.   toggle() {  
  45.     this.isExpanded = !this.isExpanded;  
  46.   }  
  47.   
  48.   getNotificationCount() {  
  49.     this.notificationService.getNotificationCount().subscribe(  
  50.       notification => {  
  51.         this.notification = notification;  
  52.       },  
  53.       error => this.errorMessage = <any>error  
  54.     );  
  55.   }  
  56.   
  57.   getNotificationMessage() {  
  58.     this.notificationService.getNotificationMessage().subscribe(  
  59.       messages => {  
  60.         this.messages = messages;  
  61.       },  
  62.       error => this.errorMessage = <any>error  
  63.     );  
  64.   }  
  65.   
  66.   deleteNotifications(): void {  
  67.     if (confirm(`Are you sure want to delete all notifications?`)) {  
  68.       this.notificationService.deleteNotifications()  
  69.         .subscribe(  
  70.           () => {  
  71.             this.closeModal();  
  72.           },  
  73.           (error: any) => this.errorMessage = <any>error  
  74.         );  
  75.     }  
  76.   }  
  77.   openModal() {  
  78.     this.getNotificationMessage();  
  79.     this.modalService.open('custom-modal');  
  80.   }  
  81.   
  82.   closeModal() {  
  83.     this.modalService.close('custom-modal');  
  84.   }  
  85. }  
Like the employee list component, we will add the singalR connection here as well.
 
 
 
Whenever, user adds a new employee or edit or delete data, notification will be shown in the connected client browsers immediately.
 
We can 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"  
  6.           aria-label="Toggle navigation" [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.             <i class="fa fa-bell  has-badge" style="cursor: pointer;" (click)="openModal()"></i>  
  18.             <div class="numberCircle" *ngIf="notification && notification?.count>0" style="cursor: pointer;"  
  19.               (click)="openModal()">  
  20.               {{notification?.count}}</div>  
  21.           </ul>  
  22.         </div>  
  23.       </div>  
  24.     </nav>  
  25.   </header>  
  26.   <footer>  
  27.     <nav class="navbar navbar-light bg-white mt-5 fixed-bottom">  
  28.       <div class="navbar-expand m-auto navbar-text">  
  29.         Developed with <i class="fa fa-heart"></i> by <b>Sarathlal  
  30.           Saseendran</b>  
  31.       </div>  
  32.     </nav>  
  33.   </footer>  
  34.     
  35.   <app-modal id="custom-modal">  
  36.     <button class="btn btn-primary" (click)="deleteNotifications();" style="margin-right: 10px;" [disabled]="notification?.count==0">Delete all Notifications</button>  
  37.     <button class="btn btn-secondary" (click)="closeModal();">Close</button>  
  38.     <div style="margin-bottom: 10px;"></div>  
  39.     <div *ngFor="let msg of messages" [ngSwitch]="msg.tranType">  
  40.       <h6 *ngSwitchCase="'Add'"><span class="badge badge-success">New employee '{{msg.employeeName}}' added</span></h6>  
  41.       <h6 *ngSwitchCase="'Edit'"><span class="badge badge-info">Employee '{{msg.employeeName}}' edited</span></h6>  
  42.       <h6 *ngSwitchCase="'Delete'"><span class="badge badge-warning">Employee '{{msg.employeeName}}' deleted</span></h6>  
  43.     </div>  
  44.   </app-modal>  
We can also modify the stylesheet 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. }  
  24. .fa-bell {  
  25.     padding-top10px;  
  26.     colorred;  
  27.   }  
  28.   
  29.   .numberCircle {  
  30.     border-radius: 50%;  
  31.     width21px;  
  32.     height21px;  
  33.     padding4px;  
  34.   
  35.     background#fff;  
  36.     border1px solid darkgrey;  
  37.     color:red;  
  38.     text-aligncenter;  
  39.     margin-left-7px;  
  40.   
  41.     font10px Arialsans-serif;  
  42. }  
Create the final home component now.
 
ng g component home
 
There is no code change for the class file. We can modify the html template file with below code.
 
home.component.html
  1. <div style="text-align:center;">  
  2.     <h3>Real-time Angular 11 Application with SignalR and .NET 5</h3>  
  3.     <p>Welcome to our new single-page Employee application, built with below technologies:</p>  
  4.     <img src="../../assets/AngularSignalR.png" width="700px">  
  5. </div>  
We must add reference for below modules in app.module class.
 
 
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 { FormsModule, ReactiveFormsModule } from '@angular/forms';  
  6. import { HttpClientModule } from '@angular/common/http';  
  7.   
  8. import { AppComponent } from './app.component';  
  9. import { EmployeeListComponent } from './employee/employee-list/employee-list.component';  
  10. import { EmployeeEditComponent } from './employee/employee-edit/employee-edit.component';  
  11. import { EmployeeDetailComponent } from './employee/employee-detail/employee-detail.component';  
  12. import { ModalComponent } from './modal/modal.component';  
  13. import { NavMenuComponent } from './nav-menu/nav-menu.component';  
  14. import { HomeComponent } from './home/home.component';  
  15.   
  16.   
  17. @NgModule({  
  18.   declarations: [  
  19.     AppComponent,  
  20.     EmployeeListComponent,  
  21.     EmployeeEditComponent,  
  22.     EmployeeDetailComponent,  
  23.     ModalComponent,  
  24.     NavMenuComponent,  
  25.     HomeComponent  
  26.   ],  
  27.   imports: [  
  28.     BrowserModule,  
  29.     AppRoutingModule,  
  30.     ReactiveFormsModule,    
  31.     FormsModule,    
  32.     HttpClientModule,    
  33.   ],  
  34.   providers: [],  
  35.   bootstrap: [AppComponent]  
  36. })  
  37. export class AppModule { }  
We must add below route values in the app-routing.module class as well.
 
app-routing.module.ts
  1. import { NgModule } from '@angular/core';  
  2. import { Routes, RouterModule } from '@angular/router';  
  3. import { EmployeeDetailComponent } from './employee/employee-detail/employee-detail.component';  
  4. import { EmployeeEditComponent } from './employee/employee-edit/employee-edit.component';  
  5. import { EmployeeListComponent } from './employee/employee-list/employee-list.component';  
  6. import { HomeComponent } from './home/home.component';  
  7.   
  8. const routes: Routes = [  
  9.   { path: '', component: HomeComponent, pathMatch: 'full' },  
  10.   {  
  11.     path: 'employees',  
  12.     component: EmployeeListComponent  
  13.   },  
  14.   {  
  15.     path: 'employees/:id',  
  16.     component: EmployeeDetailComponent  
  17.   },  
  18.   {  
  19.     path: 'employees/:id/edit',  
  20.     component: EmployeeEditComponent  
  21.   },  
  22. ]  
  23.   
  24. @NgModule({  
  25.   imports: [RouterModule.forRoot(routes)],  
  26.   exports: [RouterModule]  
  27. })  
  28. export class AppRoutingModule { }  
We can modify the app.component.html 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 entire coding part. We are ready to run our application. We can run both API and Angular app together.
 
 
Currently, we have no employee data in database. There is no notification also. We can create new employee record. At the same, we can open the application in another Edge browser as well.
 
 
 
 
After clicking the Save button, you can notice that one notification is displayed in the bell icon on the menu bar. This notification is not only displayed in this browser, it is also displayed instantaneously in the other Edge browser also.
 
 
 
Now, we can edit the employee record from Edge browser.
 
 
 
 
If you check the other Chrome browser, you can see that the employee data is updated instantly along with notification on bell icon.
 
 
 
 
You can click the bell icon, and see the entire notifications inside a popup window.
 
 

Conclusion


In this post, we have seen how to create a real-time application with Angular 11, SignalR and .NET 5. We have created an employee app which allow us to add, edit and delete employee data. We have seen how the data is displayed in other connected client browsers instantly. You can download entire source and check it from your end. I have used entity framework for database connectivity. I have already added the migration script along with source code. You can simply create the database using migration command. Please feel free to give your valuable comments after checking the application.


Similar Articles