Building real time charts with Angular 5, Google Charts, SignalR Core, .NET Core 2, Entity Framework Core 2 and SqlTable dependency, part 1

 

This series of articles aims to provide a proof of concept for building real time charts with latest technologies.

For your information this project can be reused on other projects Angular 5 and prior.

Before preparing the project, we will review the technologies used and others requirements.

Overview

All the following instructions are based on my GitHub project

Here is the architecture of the project :

Database

Sql Server, localDb with Visual Studio 2017 is correct to make it work

Front End technologies

BackEnd technologies

SignalR Core provides real time connection to database, and SqlTableDependency  checks any changes on your Sql database and notify your app when changes occur.

Setup your environment

Enable service broker

The Service Broker object, built-in from SQL Server 2005, is to provide a database messaging tool for managing data flows between SQL Servers asynchronously, serialized, and transactional.

In our project it wil allow to notify you app on any changes that occur.

ALTER DATABASE SignalRDemo SET ENABLE_BROKER with rollback immediate

Create SQL table for the project

USE [SignalRDemo]USE [SignalRDemo]GO
/****** Object:  Table [dbo].[GaugesData]    Script Date: 2017-12-30 14:51:37 ******/SET ANSI_NULLS ONGO
SET QUOTED_IDENTIFIER ON GO
CREATE TABLE [dbo].[GaugesData]( [Id] [int] NOT NULL, [Memory] [int] NOT NULL, [Cpu] [int] NOT NULL, [Network] [int] NOT NULL, 
CONSTRAINT [PK_GaugesData] PRIMARY KEY CLUSTERED ( 
[Id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
GO

Then populate table with data (minimum 0, maximum 100) for each field

Install Angular-CLI 1.6.3 or later

npm install -g angular-cli@1.6.3

Create an Angular 5 project

ng new myApp

Download and install the SignalR client side library

npm install @aspnet/signalr-client

Ensure you have installed required tools and SDK for Back End

You will need Visual Studio 2017 and .Net Core 2

Install required Nuget Packages

PM > Install-Package Microsoft.AspNetCore.SignalR -Version 1.0.0-alpha2-final 

PM > Install-Package Microsoft.EntityFrameworkCore -Version 2.0.1 

PM > Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.0.1 

PM > Install-Package SqlTableDependency -Version 6.1.0

 

Now let’s see in details how the .NET Core project works : part 2

Building real time charts with Angular 5, Google Charts, SignalR Core, .NET Core 2, Entity Framework Core 2 and SqlTable dependency, part 2

 

<< Back to part 1

Let’s take a look on what does the project look like :

 

To make it work this project contains :

  • A DbContext (GaugesContext.cs) for EntityFramework Core
  • A Hub (GaugeHub.cs) for SignalR that broadcast data
  • A Model that contains strongly typed data to send (Gauge.cs)
  • A Repository exposed with Entity Framework and its Interface (GaugeRepository.cs and IGaugeRepository.cs)
  • A Subscription to Gauge sql table with SqlTableDependency and its Interface (GaugeDatabaseSubscription.cs and IDatabaseSubscription)
  • Two Extension methods that extends IServiceCollection (AddDbContextFactory.cs) and IApplicationBuilder (UseSqlTableDependency.cs)
  • And Startup.cs and Program.cs

Let’s describe their implementation :

Gauge Model

using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace SignalrCoreDemoWithSqlTableDependency.Models
{
    [Table("GaugesData")]
    public class Gauge
    {
        [JsonProperty("id")]
        [Key]
        public int Id { get; set; }

        [JsonProperty("memory")]
        public int Memory { get; set; }

        [JsonProperty("cpu")]
        public int Cpu { get; set; }

        [JsonProperty("network")]
        public int Network { get; set; }
    }
}

Gauge DbContext

using Microsoft.EntityFrameworkCore;
using SignalrCoreDemoWithSqlTableDependency.Models;

namespace SignalrCoreDemoWithSqlTableDependency.EF
{
    public class GaugeContext : DbContext
    {
        public GaugeContext(DbContextOptions options)
            : base(options)
        {
        }

        public virtual DbSet Gauges { get; set; }
    }
}

Gauge Repository

using SignalrCoreDemoWithSqlTableDependency.EF;
using SignalrCoreDemoWithSqlTableDependency.Models;
using System;
using System.Linq;

namespace SignalrCoreDemoWithSqlTableDependency.Repository
{
    public interface IGaugeRepository
    {
       Gauge Gauge { get; }
    }
    public class GaugeRepository : IGaugeRepository
    {
        private Func _contextFactory;

        public Gauge Gauge => GetGauge();

        public GaugeRepository(Func context)
        {
            _contextFactory = context;
        }

        private Gauge GetGauge()
        {
            using (var context = _contextFactory.Invoke())
            {
                return context.Gauges.FirstOrDefault();
            }
        }
    }
}

Just take a look at how the GaugeContext is injected, I inject a Func that provide an instance of DbContext?

Why? because the lifecycle of our hub must be singleton, so all interdependencies requires between classes have to be singleton too, but we can’t use a DbContext in singleton because the Entity Framework context is not thread safe and in concurrence scenarios the context has a lot of issues.

Gauge Hub

using Microsoft.AspNetCore.SignalR;
using SignalrCoreDemoWithSqlTableDependency.Repository;
using System.Threading.Tasks;

namespace SignalrCoreDemoWithSqlTableDependency.Hubs
{
    public class GaugeHub : Hub
    {
        private readonly IGaugeRepository _repository;

        public GaugeHub(IGaugeRepository repository)
        {
            _repository = repository;
        }

        public async Task GetGaugesData()
        {
            await Clients.All.InvokeAsync("GetGaugesData", _repository.Gauge);
        }
    }
}

The Gauge Hub must inherit from Hub class

“GetGaugesData” is the name of the opretation the client will subscribe to get Gauges data (Clients.All.InvokeAsync(“GetGaugesData”, _repository.Gauge))

GaugeDatabaseSubscription

This is the nost exciting part, this class provides subscription to any modification on Gauge table and notify our app of these modifications, then SignalR will boradcoast it to the client, let’s take a look :

using Microsoft.AspNetCore.SignalR;
using SignalrCoreDemoWithSqlTableDependency.Hubs;
using SignalrCoreDemoWithSqlTableDependency.Models;
using SignalrCoreDemoWithSqlTableDependency.Repository;
using System;
using TableDependency.Enums;
using TableDependency.EventArgs;
using TableDependency.SqlClient;

namespace SignalrCoreDemoWithSqlTableDependency.SqlTableDependencies
{
    public interface IDatabaseSubscription
    {
       void Configure(string connectionString);
    }
    public class GaugeDatabaseSubscription : IDatabaseSubscription
    {
        private bool disposedValue = false;
        private readonly IGaugeRepository _repository;
        private readonly IHubContext _hubContext;
        private SqlTableDependency _tableDependency;

        public GaugeDatabaseSubscription(IGaugeRepository repository, IHubContext hubContext)
        {
            _repository = repository;
            _hubContext = hubContext;
        }

        public void Configure(string connectionString)
        {
            _tableDependency = new SqlTableDependency(connectionString, null, null, null, null, DmlTriggerType.All);
            _tableDependency.OnChanged += Changed;
            _tableDependency.OnError += TableDependency_OnError;
            _tableDependency.Start();

            Console.WriteLine("Waiting for receiving notifications...");
        }

        private void TableDependency_OnError(object sender, TableDependency.EventArgs.ErrorEventArgs e)
        {
            Console.WriteLine($"SqlTableDependency error: {e.Error.Message}");
        }

        private void Changed(object sender, RecordChangedEventArgs e)
        {
            if (e.ChangeType != ChangeType.None)
            {
                // TODO: manage the changed entity
                var changedEntity = e.Entity;
                _hubContext.Clients.All.InvokeAsync("GetGaugesData", _repository.Gauge);
            }
        }

        #region IDisposable

        ~GaugeDatabaseSubscription()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    _tableDependency.Stop();
                }

                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

Important things  :

We want to be notified on any changes on the Gaube table, thta’s why I put DmlTriggerType.All on the SqlTableDependency constructor

In Changed event we invoke the broadcast data modification via GaugeHub (_hubContext.Clients.All.InvokeAsync(“GetGaugesData”, _repository.Gauge))

AddDbContextFactory and UseSqlTableDependency

We need these extensions methods to apply in the Startup.cs  our app injection dependency for our GaugeContext and apply as well our SqlTableDependency service

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace SignalrCoreDemoWithSqlTableDependency
{
    public static class AddDbContextFactoryHelper
    {
        public static void AddDbContextFactory(this IServiceCollection services, string connectionString) where DataContext : DbContext
        {
            services.AddSingleton<Func>((ctx) =>
            {
                var options = new DbContextOptionsBuilder()
                    .UseSqlServer(connectionString)
                    .Options;

                return () => (DataContext)Activator.CreateInstance(typeof(DataContext), options);
            });
        }
    }
}
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SignalrCoreDemoWithSqlTableDependency.SqlTableDependencies;

namespace SignalrCoreDemoWithSqlTableDependency
{
    public static class UseSqlTableDependencyHelpers
    {
        public static void UseSqlTableDependency(this IApplicationBuilder services, string connectionString) where T : IDatabaseSubscription
        {
            var serviceProvider = services.ApplicationServices;
            var subscription = serviceProvider.GetService();
            subscription.Configure(connectionString);
        }
    }
}

At last Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using SignalrCoreDemoWithSqlTableDependency.EF;
using SignalrCoreDemoWithSqlTableDependency.Hubs;
using SignalrCoreDemoWithSqlTableDependency.Repository;
using SignalrCoreDemoWithSqlTableDependency.SqlTableDependencies;

namespace SignalrCoreDemoWithSqlTableDependency
{
    public class Startup
    {
        private const string ConnectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=SignalRDemo;Integrated Security=SSPI;";
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSignalR();

            // dependency injection
            services.AddDbContextFactory(ConnectionString);
            services.AddSingleton<IGaugeRepository, GaugeRepository>();
            services.AddSingleton<IDatabaseSubscription, GaugeDatabaseSubscription>();
            services.AddSingleton<IHubContext, HubContext>();

            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader());
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseCors("CorsPolicy");

            app.UseSignalR(routes =>
            {
                routes.MapHub("gauges");
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseSqlTableDependency(ConnectionString);

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

 

The back end is now completly set up 🙂

Let’s go to see how we setup the front end: part 3

Using Google Charts in Angular 4 project, part 2

Integrate Google Charts and make reusable chart components

We previously saw how to use Google Charts in an classical HTML5 / Javascript page, now it’s time to see how we can make it work in an Angular 4 project. We will still use use the chart as sample (Google Pie Chart)

Let’s get started !

Step 1 :

Add the javascript library file in index.html :

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

Step 2 :

To make Google Charts more reusable as possible, let’s make a class named GoogleChartsBaseService that does the redundant job : loading the library and manage the callback that we described in the previous article:

declare var google: any;

export class GoogleChartsBaseService {
  constructor() { 
    google.charts.load('current', {'packages':['corechart']});
  }

  protected buildChart(data: any[], chartFunc: any, options: any) : void {
    var func = (chartFunc, options) => {
      var datatable = google.visualization.arrayToDataTable(data);
      chartFunc().draw(datatable, options);
    };   
    var callback = () => func(chartFunc, options);
    google.charts.setOnLoadCallback(callback);
  }
  
}

As you can I added this instruction at the top : declare var google: any;

This is because with TypeScript, and Typescript doesn’t know the definition of google variable, so we declared it, corresponding to the global variable declared in Google Charts library. Typescript will be able to transpile it.

buildChart encapsulate the callback definition, the data mapping (Array to Datatable), and the setOnLoadCallback method call.

Step 3 :

Let’s make a PieChartConfig model to bind it’s configuration :

export class PieChartConfig {
    title: string;
    pieHole: number

    constructor(title: string, pieHole: number) {
        this.title = title;
        this.pieHole = pieHole;
    }
}

title member is the chart title

pieHole is corresponding to the ratio of radii between the hole and the chart (value from 0 to 1)

Step 4 :

Let’s build GooglePieChartService , this service will extends GoogleChartsBaseService 

Because we use Typescript as you know, let’s add at the top : declare var google: any;

We need to encapsulate object google.visualization in a callback (arrow function works too) because this object is not set yet by the framework (during execution of setOnLoadCallback)

Here we are :

import { GoogleChartsBaseService } from './google-charts.base.service';
import { Injectable } from '@angular/core';
import { PieChartConfig } from './../Models/PieChartConfig';

declare var google: any;

@Injectable()
export class GooglePieChartService extends GoogleChartsBaseService {

  constructor() { super(); }

  public BuildPieChart(elementId: String, data: any[], config: PieChartConfig) : void {  
    var chartFunc = () => { return new google.visualization.PieChart(document.getElementById(elementId)); };
    var options = {
            title: config.title,
            pieHole: config.pieHole,
      };

    this.buildChart(data, chartFunc, options);
  }
}

Because it’s an injectable service (singleton), don’t forget to add it in providers array in app.module.ts

We built a base service and PieChartService, that means the logic is set in services and it provides more flexibilty in your app, we decoupled as much as possible Google Charts with our app

Step 5 :

It’s time to make reusable components

we need 3 parameters in input

1- Html id attribute of the div we want to fill with the chart

2- The chart configurations options

3- Data

Here we are:

import { Component, Input, OnInit } from '@angular/core';

import { GooglePieChartService } from './../../Services/google-pie-chart.service';
import { PieChartConfig } from './../../Models/PieChartConfig';

declare var google: any;


@Component({
  selector: 'pie-chart',
  templateUrl: './piechart.component.html'
})
export class PieChartComponent implements OnInit {

    @Input() data: any[];
    @Input() config: PieChartConfig;
    @Input() elementId: String;

    constructor(private _pieChartService: GooglePieChartService) {}

    ngOnInit(): void {
        this._pieChartService.BuildPieChart(this.elementId, this.data, this.config); 
    }
}

the pie chart conponent html file :

<div id="{{elementId}}" style="width: 800px; height: 400px;"></div>

We definitely have to declare them into app.module.ts as well

Step 6 :

Let’s add data, config and try !

We will build a component (named DashboardComponent) that display our reusable ours Pie charts and provide them data.

Ts file :

import { ComboChartConfig } from './../Models/ComboChartConfig';
import { Component } from '@angular/core';
import { PieChartConfig } from './../Models/PieChartConfig';

@Component({
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent {
  title = 'Reusable charts sample';

  data1: any[];
  config1: PieChartConfig;
  elementId1: String;

  data2: any[];
  config2: PieChartConfig;
  elementId2: String;
  
  ngOnInit(): void {     

    //Piechart1 Data & Config
    this.data1 = [['Task', 'Hours per Day'],
    ['Eat',      3],
    ['Commute',  2],
    ['Watch TV', 5],
    ['Video games', 4],
    ['Sleep',    10]];

    this.config1 = new PieChartConfig('My Daily Activities at 20 years old', 0.4);
    this.elementId1 = 'myPieChart1';

    //Piechart2 Data & Config
    this.data2 = [['Task', 'Hours per Day'],
                  ['Work',     11],
                  ['Eat',      2],
                  ['Commute',  2],
                  ['Watch TV', 2],
                  ['Sleep',    7]]

    this.config2 = new PieChartConfig('My Daily Activities at 30 years old', 0.4);
    this.elementId2 = 'myPieChart2';
  }

}

HTML file :

<h2>{{title}}</h2>

<div class="divTable">
    <div class="divTableBody">
        <div class="divTableRow">
            <div class="divTableCell"><pie-chart [data]="data1" [config]="config1" [elementId]="elementId1"></pie-chart></div>
            <div class="divTableCell"><pie-chart [data]="data2" [config]="config2" [elementId]="elementId2"></pie-chart></div>
        </div>
    </div>
</div>

And here is the associated css in dashboard.component.css

Don’t forget to declare that component into app.module.ts

And now …..

Awesome right? 🙂

Now you know how to make reusable Google Charts Angular 4 components! 🙂 🙂 🙂

Here the Url of the project if you want to test in your browser : http://demoangular4.azurewebsites.net/

If you want to get the complete source code you can find it there, you can also contribute! : https://github.com/AnthonyGiretti/Angular4-GoogleCharts