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

 

<< Back to part 2

We are done with the project setup and the back end implementation, now let’s focus the front end solution.

What does it look like ?

 

We have :

  • A folder that contains the gauge chart component (gaugeschart.component.html and gaugeschart.component.ts)
  • A folder that contains a gauge chart service and a Google Charts base service (google-gauges-chart.service.ts and google-charts.base.service.ts)
  • A folder that contains environments files
  • A folder that contains a strongly typed model for the gauge chart (gauge.ts)
  • Finally at the root of src folder the defaults files components and module (app component files and app module file)

 

Implementation

Gauge.ts

export class GaugeModel {
    public id: number;
    public cpu: number;
    public memory: number;
    public network: number;
}

google-charts.base.service.ts and google-gauges-chart.service.ts

For more explanations about these files work, you can check a preivous article I wrote some times before : http://anthonygiretti.com/2017/10/12/using-google-charts-in-angular-4-project-part-2/

declare var google: any;

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

  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);
  }
  
}

 

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

declare var google: any;

@Injectable()
export class GoogleGaugesChartService extends GoogleChartsBaseService {

  constructor() { super(); }

  public BuildGaugesChart(elementId: String, data: any[], config: any) : void {
    var chartFunc = () => { return new google.visualization.Gauge(document.getElementById(elementId)); };
    this.buildChart(data, chartFunc, config);
  }
}

Gauge chart component

As you can see, the component is decorelated from Google Charts because I inject within a Google Gauge chart service, it can be easily replaced by another Javascript library such as canvas.js

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

import { GoogleGaugesChartService } from '../services/google-gauges-chart.service';
import { OnChanges } from '@angular/core/src/metadata/lifecycle_hooks';

declare var google: any;


@Component({
  selector: 'gauges-chart',
  templateUrl: './gaugeschart.component.html'
})
export class GaugesChartComponent implements OnInit, OnChanges {

    @Input() data: any[];
    @Input() config: any;
    @Input() elementId: String;
    
    constructor(private _gaugesChartService: GoogleGaugesChartService) {}

    /* Only chart data !!!!!!! */
    ngOnChanges(changes) {
        if (changes.data != undefined) {
            this.data = changes.data.currentValue;
            this._gaugesChartService.BuildGaugesChart(this.elementId, this.data, this.config);
        }      
    }

    ngOnInit(): void {
        this._gaugesChartService.BuildGaugesChart(this.elementId, this.data, this.config); 
    }
}

I used ngOnInit for the component initialization with its first binding to data, then we need to “listen” any moditication of data that are broadcasted with the specific event ngOnChanges and we ensure it’s data that are modified if (changes.data != undefined)

Component Html :

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

App component

This is most insteresting part, we will see hoz the client side library for SignalR works

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

import { environment as Environment } from '../environments/environment';
import { GaugeModel } from '../models/gauge';
import { HubConnection } from '@aspnet/signalr-client';

declare var google: any;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  private _hubConnection: HubConnection;

  public data:any[] = [
    ['Label', 'Value'],
    ['Memory', 0],
    ['CPU', 0],
    ['Network', 0]
  ];
  public elementId:String = "Gauge1";
  public config:any = {
    width: 400, height: 120,
    redFrom: 90, redTo: 100,
    yellowFrom:75, yellowTo: 90,
    minorTicks: 5
  };


  constructor() {
  }

  public ngOnInit() {
    
    this._hubConnection = new HubConnection(Environment.hubUrl);
    
    this._hubConnection
      .start()
      .then(() => this._hubConnection.invoke('GetGaugesData').catch(err => console.error(err)))
      .catch(err => console.log('Error while establishing connection :('));

    var that = this;
    this._hubConnection.on('GetGaugesData', (data: GaugeModel) => {
      this.data = [
        ['Label', 'Value'],
        ['Memory', data.memory],
        ['CPU', data.cpu],
        ['Network', data.network]
      ];
    });
    
  }

}

Firstly, we need to isntantiate a HubConnection, like this : this._hubConnection = new HubConnection(Environment.hubUrl); it takes in parameter the Hub Url defined in environment.ts :

export const environment = {
production:false,
hubUrl:"http://localhost:33383/gauges"
};

Then we open the connection and define a callback when it succeeds or handle error when it occur :

this._hubConnection
.start()
.then(() => this._hubConnection.invoke('GetGaugesData').catch(err => console.error(err)))
.catch(err => console.log('Error while establishing connection :('));

When it succeed we invoke the method that populate initially our chart :

this._hubConnection.invoke('GetGaugesData')

Once we are done with the inialization, we subscribe to any change and modify data :

this._hubConnection.on('GetGaugesData', (data: GaugeModel) => {
      this.data = [
        ['Label', 'Value'],
        ['Memory', data.memory],
        ['CPU', data.cpu],
        ['Network', data.network]
      ];
    });

Component Html :

<h2>Real-Time Gaugeschart by Google with SignalR Core and EntityFramework Core 2 / SQLTableDependency</h2>

<div class="divTable">
   <divclass="divTableBody">
      <divclass="divTableRow">
         <divclass="divTableCell"><gauges-chart [data]="data" [config]="config" [elementId]="elementId"></gauges-chart></div>
       </div>
   </div>
</div>

Component Css :

.divTable{
    display: table;
    width: 100%;
}

.divTableRow {
    display: table-row;
}

.divTableHeading {
    background-color: #EEE;
    display: table-header-group;
}

.divTableCell, .divTableHead {
    border: 1px solid #999999;
    display: table-cell;
    padding: 3px10px;
    text-align: center;
    margin: auto;
}

.divTableHeading {
    background-color: #EEE;
    display: table-header-group;
    font-weight: bold;
}

.divTableFoot {
    background-color: #EEE;
    display: table-footer-group;
    font-weight: bold;
}

.divTableBody {
    display: table-row-group;
}

Appmodule.ts :

import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
import { GaugesChartComponent } from './charts/gaugeschart.component';
import { GoogleChartsBaseService } from './services/google-charts.base.service';
import { GoogleGaugesChartService } from './services/google-gauges-chart.service';
import { NgModule } from '@angular/core';

@NgModule({
declarations: [
AppComponent,
GaugesChartComponent
],

imports: [
BrowserModule
],

providers: [GoogleChartsBaseService,GoogleGaugesChartService],
bootstrap: [AppComponent]
})

export class AppModule { }

Index.html

<!doctype html>
<html lang="en">
<head>
   <metacharset="utf-8">
   <title>Angular5SignalRCoreEFCore2GoogleCharts</title>
   <basehref="/">
   <script type="text/javascript"src="https://www.gstatic.com/charts/loader.js"></script>
   <metaname="viewport"content="width=device-width, initial-scale=1">
   <linkrel="icon"type="image/x-icon"href="favicon.ico">
</head>
<body>
    <app-root></app-root>
</body>
</html>

Dont forget to put Google Charts reference 🙂 (loader.js)

Tha ‘s the end did you enjoy it? 🙂

Try to play with data ! 😮

What’s new in TypeScript 2.6 ?

 

Version 2.6 of Microsoft’s TypeScript language has been changed to Release Candidate. This new version comes with more rigor in the audit to help developers better detect errors. The language introduces a strict check flag that is introduced by –strictFunctionTypes. The TypeScript language program manager explains that in strict verification any function that does not come from a method has its parameters compared “contravariantly”.

Typically, TypeScript compared the parameters in a “bivariant” way, which brings benefits such as a simpler model for working with arrays. TypeScript 2.6 has found a compromise by increasing the rigor on all types of functions except methods while allowing modeling of use cases such as event handlers and simplified table management.

Microsoft’s JavaScript language, which was five years old on Oct. 1, has gained momentum, so much so that it has been used in the recent rewriting of the Angular JavaScript framework. Version 2.5 of TypeScript was released at the end of August. Another new feature introduced by TypeScript 2.6 is error-killing comments using // @ ts-ignore comments. Microsoft avoided the removal of errors in TypeScript, because in most cases where users requested that they be removed, errors could be resolved by more precise configuration files, or by using an “any” assertion, said Rosenwasser.

He adds that “however, over time, we have seen two motivating examples of migration from JavaScript to TypeScript overcoming type checks that reside in legacy code.” When migrating JavaScript to TypeScript, removing errors can be very useful for developers because they often run into a model that is difficult to model. Rather than spend a lot of time trying to understand the model, developers can now postpone this work and concentrate on their actual work, thanks to the deletion comments that make it possible to ignore errors that do not hinder not the proper functioning of the code.

To override type checks in existing code, some large organizations update project dependencies in tandem. As a result, any code changes that introduce a type verification error will require a fix to avoid breaking the code generation job. According to Rosenwasser, “Even though error detection is useful, the reality is often that the code continues to work and that development teams have limited resources. Microsoft recommends that you use the delete comments sparingly and always provide an explanation to help understanding.

With TypeScript 2.6, model strings tagged in a module are now cached after the initial call. This new feature of TypeScript puts it as close to the latest revisions to the ECMAScript specification that underlies JavaScript. The standalone TypeScript compiler now provides localized messages via NPM when using the –locale flag. The mode –watch, for the emission of modules, has been accelerated.

Support improvements are planned for tools such as Visual Studio and Visual Studo Code with this release. Version 2.6 of the language allows developers to refactor comments in the JSDoc documentation into TypeScript annotations. Organizational changes have been made to the DOM declarations in lib.d.ts. TypeScript 2.6 can be installed via NPM by running the command npm install -g typecript @ rc or via NuGet. It can also be installed via Visual Studio 2017.

Angular 5, what’s new?

After a release in March 2017 of Angular 4, it is already time to welcome Angular 5 pending Angular 6 scheduled for March 2018. For those who do not know, Angular is the name of a tool set to provided by Google that serves to give happiness to javascript application developers. Happiness because the tool is complete, the community is large and it allows us to focus with pleasure on customer issues.

Among all the changes, we invite you to discover the most significant ones.

Build optimization

As of version 5.0.0, production builds created with Angular CLI will apply the default optimization.

The optimization tool included in Angular CLI has two main objectives. The first is to improve the tree shaking (removing parts of the application that are not needed). The second is to remove the Angular decorators that only the compiler needs.

Each of these actions reduces the size of the build and speeds up the startup of applications.

Angular Universal State Transfer API and DOM Support

Small reminder, Angular Universal allows to have a server-side pre-rendering Angular applications, which allows to take into account the search engines that do not manage javascript.

With Angular 5.0.0, it’s easier to share data between the server version and the client version. This eliminates the need for a duplicate HTTP call to retrieve data when the client version regains control.

Improved transpiler

Improvement of the incremental build, which makes it possible to accelerate the rebuilds notably for the production builds and those with AOT (Ahead-of-Time compilation).

Ability to remove the whitespaces, which until then were kept in templates. It is possible to configure it either at the level of the decorator of each component or at the global level via the file tsconfig.json. The default behavior is “true”, but it is envisaged that in the future it will be “false”.

At the component level :

@Component(
{ 
    templateUrl: 'about.component.html', 
    preserveWhitespaces: false 
} 
export class AboutComponent {}

In the tsconfig.json file :

{ 
   "extends": "../tsconfig.json", 
   "compilerOptions": { 
   "outDir": "../out-tsc/app", 
   "baseUrl": "./", 
   "module": "es2015", 
   "types": [] 
   }, 
   "angularCompilerOptions": { "preserveWhitespaces": false }, 
   "exclude": [ "test.ts", "**/*.spec.ts" ] 
}

Replacing the ReflectiveInjector with the StaticInjector

Again, this eliminates even more polyfills, reducing the size of applications for most developers.

Before :

ReflectiveInjector.resolveAndCreate(providers);

After :

Injector.create(providers);

Improved Zone Speed

Zone is faster by default and it is now possible to bypass zone completely for applications that need performance.

To bypass zone, here is the procedure to follow :

platformBrowserDynamic().bootstrapModule(AppModule, {ngZone: 'noop'}).then( ref => {} );

exportAs

We can now give several names to our components and directives, which is very convenient in case of renaming, it avoids breaking the existing code.

This possibility has been used in the case of prefix change of Angular Material.

Exemple :

@Component(
{ 
   moduleId: module.id, 
   selector: 'a[mat-button], a[mat-raised-button], a[mat-icon-button], a[mat-fab], a[mat-mini-fab]', 
   exportAs: 'matButton, matAnchor', . . . 
}

HttpClient

The old @angular/http module is now officially deprecated and replaced by @angular/common/http, the new HttpClient introduced in 4.3. You can probably expect that @angular/http will be removed in Angular 6.0.

HttpClient has been slightly improved with Angular 5.0, as we are now able to directly use object literals as headers or parameters, whereas we had to use the classes HttpHeaders and HttpParams.

Exemple :

const headers = new HttpHeaders().set('Authorization', 'secret');
const params = new HttpParams().set('page', '1');
return this.http.get('/api/users', { headers, params });

simplified into :

const headers = { 'Authorization': 'secret' };
const params = { 'page': '1' };
return this.http.get('/api/users', { headers, params });

Forms

Forms have a tiny but really useful addition to their API: the ability to decide when the validity and value of a field or form is updated. This is something we already had in AngularJS 1.x, but not yet in Angular.

To do so, the FormControl allows to use an options object as the second parameter, to define the synchronous and asynchronous validators, and also the updateOn option. Its value can be:

  • change, it’s the default: the value and validity are updated on every change;
  • blur, the value and validity are then updated only when the field lose the focus;
  • submit, the value and validity are then updated only when the parent form is submitted.

Exemples :

this.passwordCtrl = new FormControl('', {
 validators: Validators.required,
 updateOn: 'blur'
});
<input [(ngModel)]="user.login" [ngModelOptions]="{ updateOn: 'blur' }">
<form [ngFormOptions]="{ updateOn: 'submit' }">

RxJS 5.5

improved import

Before :

import 'rxjs/add/operator/map'; 
import 'rxjs/add/operator/filter';

After :

import { map, filter } from 'rxjs/operators';

New events for the Router

  • ChildActivationStart
  • ChildActivationEnd

These new events can be used to control the display of a spinner or to measure the performance of a resolver.

Here’s an example of how to start and stop a spinner :

class MyComponent { 
   constructor(public router: Router, spinner: Spinner) { 
      router.events.subscribe(e => { 
         if (e instanceof ChildActivationStart) { 
            spinner.start(e.route); 
         } else if (e instanceof ChildActivationEnd) { 
           spinner.end(e.route); 
         }  
       ); 
     } 
}

I18n

The messages extracted from your application now include the interpolations used in the template.

Before :

<source>
 Welcome to Ponyracer
 <x id="INTERPOLATION"/>
 <x id="INTERPOLATION_1"/>!
</source>

After :

<source>
 Welcome to Ponyracer
 <x id="INTERPOLATION" equiv-text="{{ user.firstName }}"/>
 <x id="INTERPOLATION_1" equiv-text="{{ user.lastName }}"/>!
</source>

A notable change in i18n is that the i18n comments are now deprecated. In Angular 4, you could use:

<!--i18n: @@home.justText -->
 I don't output an element, just text
<!--/i18n-->

Starting with Angular 5, you are encouraged to use an already possible alternative with ng-container:

<ng-container i18n="@@home.justText">
 I don't output an element, just text
</ng-container>

Other breaking changes

Using a different locale than the default one (en-US) now requires to load additional locale data:

import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';

registerLocaleData(localeFr);

All the i18n pipes now take a locale as their last parameter, allowing to dynamically override it:

@Component({
 selector: 'ns-locale',
 template: `
 <p>The locale is {{ locale }}</p>
 <!-- will display 'en-US' -->

<p>{{ 1234.56 | number:'1.0-3':'fr-FR' }}</p>
 <!-- will display '1 234,56' --> 
 `
})
class DefaultLocaleComponentOverridden {
 constructor(@Inject(LOCALE_ID) public locale: string) { }
}

The currency pipe now takes a string as it second parameter, allowing to chose between ‘symbol’ (default), ‘symbol-narrow’ or ‘code’. For example, with canadian dollars:

<p>{{ 10.6 | currency:'CAD' }}</p>
<!-- will display 'CA$10.60' -->

<p>{{ 10.6 | currency:'CAD':'symbol-narrow' }}</p>
<!-- will display '$10.60' -->

<p>{{ 10.6 | currency:'CAD':'code':'.3' }}</p>
<!-- will display 'CAD10.600' -->

if you want to keep the “old” pipes for now, as they have been kept in a new module DeprecatedI18NPipesModule, that you can import if you want to still use them:

@NgModule({
 imports: [CommonModule, DeprecatedI18NPipesModule],
 // ...
})
export class AppModule {

If you have something like this :

platformBrowserDynamic([
 MyCustomProviderA,
 MyCustomProviderB // depends on MyCustomProviderA
]).bootstrapModule(AppModule);

It must now be in 5.0 :

platformBrowserDynamic([
 { provide: MyCustomProviderA, deps: [] },
 { provide: MyCustomProviderB, deps: [MyCustomProviderA] }
]).bootstrapModule(AppModule);

That’s it 🙂