Haciendo gráficas en “tiempo real” con Angular, ChartJS y  NodeJS

Aprende cómo utilizar ChartJS en Angular, desde su integración hasta ejemplos

Actualmente hacer tiempo real es bastante fácil y más con WebSockets, pero en este tutorial haremos todo bastante “rústico”, es decir, haremos un servidor con NodeJS que simplemente arranque y nos tire datos aleatorios para agarrarlos con Angular.

¿Por qué digo que rústico?, bueno, en este caso pensaremos que la API de NodeJS estará orientada a ser una API Rest que no podemos manejar y solamente podemos sacarle datos cada X tiempo. En este caso, lo haremos cada 500 milisegundos.

Primero lo primero…

Antes que empezar con Angular, quiero empezar con NodeJS, lo primero será iniciar nuestra app, asumiendo que ya tienen NodeJS instalado, lo primero es iniciar una nueva aplicación, para ello vamos a crear un directorio y entrar en el desde consola. Una vez dentro, ejecutamos npm init.

Seguiremos los pasos que nos pide NPM y lo configuramos al gusto o si sencillamente lo hacen para iniciar rápido, presionan a todo enter y hasta el final nos indica que si todo está correcto tal cual y le damos enter una vez más.

Ahora que ya terminamos de crear nuestro package.json vamos a crear un archivo llamado index.js o si le asignaron un nombre en la configuración con ese nombre. Una vez creado, vamos a instalar Express con:

npm install express --save

Ya que termine nuestra instalación vamos a editar este archivo index.js con el siguiente contenido:

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('API inicializada en el puerto 3000');
});

Este código hará que arranque nuestra aplicación en el puerto 3000 en localhost . Por ahora, lo probaremos con node index.js y veremos que si accedemos a http://localhost:3000 nos muestra “Hello World!” y un aviso en consola.

Para finalizar, vamos a cambiar app.get('/', function(req, res) { ... }) dentro de las llaves agregando el siguiente código:

res.send({ data: Math.floor(Math.random() * (1000 - 1) + 1), code: 200, error: false });

Antes de terminar, necesitamos que abra el servicio a otros fuera de la aplicación, es decir, que permita a externos hacernos peticiones, esto se conoce como CORS (Control de Acceso HTTP) y para ello necesitamos una librería (hacemos este paso porque estamos pensando que, esta no es nuestra API y que estamos conectándonos a algo externo, #JustForFun pero no es requerido en local):

Ejecutamos el comando:

npm install cors

Y con esto se instalará, ya solamente falta cargarla:

var cors = require('cors');
app.use(cors());

Al final nuestro código luce de la siguiente forma:

var express = require('express');
var cors = require('cors');
var app = express();
app.use(cors());

app.get('/', function (req, res) {
  res.send({ data: Math.floor(Math.random() * (1000 - 1) + 1), code: 200, error: false });
});

app.listen(3000, function () {
  console.log('API inicializada en el puerto 3000');
});

Para poder ver la respuesta, vamos a detener en consola la aplicación y volver a ejecutarla con node index.js esto ya que Node no hace recarga automática al modificar el código, si quisiéramos hacerlo existen librerías como Nodemon o PM2 pero no lo veremos en este tutorial.

Vamos con Angular…

Ya que tenemos nuestra API funcionando, vamos a empezar con Angular, en este caso, creamos nuestra nueva app, pensando que, ya tenemos Angular-CLI instalado corremos el comando ng new charts (yo le puse charts por gráficas, tu puedes nombrarlo como gustes).

Ya que se haya creado entramos al directorio de la app y ejecutamos la aplicación con ng serve y veremos que corre sobre http://localhost:4200 por lo que podemos mantener en nuestra máquina ambos corriendo sin problemas.

Veremos si accedemos a la URL, el “hello world” de Angular-CLI, por lo que vamos a editar el app.component.html y vamos a borrarle el contenido. En este caso, Angular sí recarga de forma automática cuando hacemos un cambio, por lo que aquí no hay necesidad de “apagar y volver a prender”.

Ahora bien, antes de empezar a trabajar, vamos a instalar ChartJS, por lo que detenemos nuestro servidor de Angular (ctrl+c en Windows), el de Node puede seguir sin problemas.

Para instalar ChartJS en Angular, sencillamente ejecutamos el comando:

npm install chart.js --save

Y listo, NPM se hará cargo de instalar la dependencia, ahora hay que cargarla, ya que en este caso, ChartJS no es una dependencia orientada hacia TypeScript y aunque hay varias librerías que se encargan de manejar esta dependencia para que podamos implementarla Angular-Style en este caso la vamos a manejar como librería “suelta”.

Ya que se instaló la librería, vamos a encender de nueva cuenta el servidor de Angular con ng serve una vez más.

Dentro de nuestro app.component.ts lo primero que haremos será generar 2 funciones, la primera va a ser la que llame los datos desde nuestro servidor, la segunda será una función que se va a ejecutar cada 100 milisegundos y que incluirá a la primera. Es por ello que hacemos las funciones con el siguiente código (solo la que obtiene el la respuesta del API tiene contenido por ahora):

/**
 * Print the data to the chart
 * @function showData
 * @return {void}
 */
 private showData(): void {
  
 }
  
 /**
 * Get the data from the API
 * @function getFromAPI
 * @return {Observable<any>}
 */
 private getFromAPI(): Observable<any>{
   return this.http.get(
      'http://localhost:3000',
      { responseType: 'json' }
   );
 }

Si guardamos esto (dentro de la clase AppComponent obviamente) veremos que la consola nos dice un error:

ERROR in src/app/app.component.ts(24,24): error TS2304: Cannot find name ‘Observable’.

Esto significa que el Observable que estamos retornando de la función getFromAPI() no existe, es como decir que queremos crear una instancia de un objeto llamado SalonDeClases pero la clase no ha sido llamada, por lo tanto, no sabe el programa qué es SalonDeClases , para ello, vamos a importar esta librería junto a la librería que nos permite o facilita hacer las llamadas HTTP al servidor.

Hasta la parte superior de nuestro archivo agregamos:

import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';

Justo debajo de:

import { Component } from '@angular/core';

Ya solo nos falta importar un módulo, justo en esta sección que menciono del @angular/core vamos a poner: { Component, OnInit, OnDestroy } para que importe funcionalidades que vamos a necesitar.

Ahora, si guardamos en este punto todo nuestro código, nos mostrará un error:

ERROR in src/app/app.component.ts(28,16): error TS2339: Property ‘http’ does not exist on type ‘AppComponent’.

¿Qué significa esto?, que no hemos aún pasado la instanciahttp del módulo HttpClient por lo que la función que obtiene de nuestro API aún no puede arrancar.

Vamos a solucionar esto, para ello agregamos un constructor a nuestra clase:

/**
 * Constructor
 */
 constructor(private http: HttpClient) {
  
 }

Y listo, con esto, ya tenemos casi todo preparado, ¿qué falta?, importar este mismo, dentro del módulo de app.module para que se cargue. Dentro de este archivo vamos a agregar:

import { HttpClientModule, HttpClient } from '@angular/common/http';

Hasta la parte superior del archivo y luego, en la parte de imports incluiremos el HttpClientModule

imports: [
    BrowserModule,
    HttpClientModule
],

Una vez hecho esto, guardamos y veremos que ya no hay errores ni en consola ni en la consola de nuestro navegador, vamos a volver a nuestra función showData.

En esta función lo que haremos primeramente será crear una variable interna de la clase para controlar el setInterval y que si tenemos una app con rutas, cada vez que la instancia del componente actual se destruya, este intérvalo se destruya consigo.

/**
 * Interval to update the chart
 * @var {any} intervalUpdate
 */
 private intervalUpdate: any = null;

Esto debajo de la clase (o del export class AppComponentmejor dicho) creará dicha variable. Hasta aquí, ya tenemos nuestra base, es hora de crear bien la función y llamar a ChartJS para darle el “real time” que buscamos

A darle con el código…

Ya que tenemos todo preparado, vamos a crear como dije, la función que hará todo el trabajo de la gráfica, para ello necesitamos 3 cosas previamente:

Una variable que guarde el objeto de ChartJS:

/**
 * The ChartJS Object
 * @var {any} chart
 */
 public chart: any = null;

La inicialización del intérvalo, esto deberá ir dentro de la función ngOnInit que es la que se ejecuta una vez el componente se inicializó:

/**
 * On component initialization
 * @function ngOnInit
 * @return {void}
 */
 private ngOnInit(): void {
  this.intervalUpdate = setInterval(function(){
   this.showData();
  }.bind(this), 500);
 }

Y por último, en el ngOnDestroy vamos a detener este intérvalo.

/**
 * On component destroy
 * @function ngOnDestroy
 * @return {void}
 */
 private ngOnDestroy(): void {
  clearInterval(this.intervalUpdate);
 }

Ahora que tenemos todo vamos a probar que imprima en consola para corroborar que se ejecuta correctamente.

En la función showData solamente agregar un console.log(1); y si vemos muchos “1” en la consola del navegador, está todo bien.

Si está todo bien, entonces, vamos a eliminar este console.log y vamos a crear la instancia del objeto de ChartJS, esta instancia se crea dentro del ngOnInit y asignaremos configuraciones muy básicas:

this.chart = new Chart('realtime', {
   type: 'line',
   data: {
    labels: [],
    datasets: [
      {
     label: 'Data',
     fill: false,
     data: [],
     backgroundColor: '#168ede',
     borderColor: '#168ede'
      }
    ]
     },
     options: {
    tooltips: {
     enabled: false
    },
    legend: {
     display: true,
     position: 'bottom',
     labels: {
      fontColor: 'white'
     }
    },
    scales: {
      yAxes: [{
       ticks: {
        fontColor: "white"
       }
      }],
      xAxes: [{
     ticks: {
      fontColor: "white",
      beginAtZero: true
     }
      }]
    }
     }
  });

Como veremos, simplemente haremos una gráfica de tipo línea, que empiece desde 0 con algunos colores y sin data ni relleno, cabe mencionar que hay partes como las leyendas que “no se verán” por estar en blanco, más adelante sabremos por qué. En este punto nos saltarán 2 ( o 1) errores, el primero es un error que nos indica que Chart no es un objeto válido, ¿por qué?, porque no hemos implementado su importación, y el segundo (que no a todos puede que les salte) es que aún importando, dirá que no lo encuentra.

Bueno, el primer error se soluciona haciendo su importación:

import { Chart } from 'chart.js';

Como siempre hasta la parte superior del archivo y luego, este segundo error, lo vamos a solucionar abriendo el archivo .angular-cli.json que está en la raíz de nuestra aplicación. En este archivo buscamos el apartado "scripts": [] y le agregaremos: "../node_modules/chart.js/dist/Chart.js" (incluyendo las comillas, tal cual) entre los corchetes y listo, debería solucionar el problema.

Una vez que esté lista la importación y solucionados los problemas, continuamos.

Dentro de la función vamos a incluir el siguiente código:

this.getFromAPI().subscribe(response => {
   
  }, error => {
   console.error("ERROR: Unexpected response");
  });

Esto hará que por fin empiece a jalar datos del API y si hay una falla, nos tire un error en consola.

Cabe destacar, que hasta aquí, si accedemos al Network de la consola veremos peticiones como loco. Pero así va a funcionar por ahora, no te preocupes, nuestro local es muy potente y logrará resistir.

Vamos a asignar ahora la respuesta a nuestra gráfica antes de ya pasarla al HTML.

Dentro del apartado del response, vamos a incluir el código:

if(response.error === false) {
    let chartTime: any = new Date();
    chartTime = chartTime.getHours() + ':' + ((chartTime.getMinutes() < 10) ? '0' + chartTime.getMinutes() : chartTime.getMinutes()) + ':' + ((chartTime.getSeconds() < 10) ? '0' + chartTime.getSeconds() : chartTime.getSeconds());
    this.chart.data.labels.push(chartTime);
    this.chart.data.datasets[0].data.push(response.data);
    this.chart.update();
   } else {
    console.error("ERROR: The response had an error, retrying");
   }

Básicamente, esto lo que hace es crear un mini “reloj” (por así decirlo) para darnos idea solamente como “label” de en qué momento se hizo la carga de la información, después, asignamos este “reloj” a los labels de la gráfica.

Después, en el dataset 0 (ya que solo hay 1 dataset) le asignamos la respuesta del API en el objeto data que previamente definimos y actualizamos la gráfica.

Cabe destacar 2 cosas por ahora, la primera es que incluí if(response.error === false) esto porque, en el API lo anexé, imaginemos que, en realidad hacemos solicitudes a un API y esta, responde de alguna forma dentro que hubo un error en la consulta de su lado, pero responde con HTTP OK (es decir, como si no hubiera muerto el API ni controlasen por errores de HTTP), entonces, no sabríamos qué onda y en este caso, solamente es para aparentar.

El segundo punto es que esto va a inyectar una y otra y otra vez información en la gráfica, pero, esto lo acomodaremos al final del tutorial.

Ya que tenemos esto vamos al html en el app.component.html , dentro de este, vamos a incluir el código:

<div style="width: 500px; height: 500px;">
 <h1>Realtime</h1>
 <canvas id="realtime">{{ chart }}</canvas>
</div>

Como podemos ver, ya se muestra la gráfica y empieza a actualizarse con mucha constancia pero, en algún punto temprano se satura. Vamos a solucionar esto y darle un poco más de estilo, ¿no lo creen?.

Para solucionar el primer apartado, vamos a darle “15 steps” o “15 puntos a mostrar”. Dentro del código que creamos vamos a modificar agregando una condicional, esto arriba de this.chart.data.labels.push(chartTime) :

if(this.chart.data.labels.length > 15) {
      this.chart.data.labels.shift();
      this.chart.data.datasets[0].data.shift();
}

Y esto permitirá que una vez que alcancemos 15 “labels” borre el primero y como se ejecutará con constancia, gracias a shift() borrara el primer label (o el label más viejo) y mostrará con constancia.

Ahora, por último vamos a estilizar, para ello, vamos a quitar el código HTML previo de la gráfica y agregar el siguiente:

<div class="card">
 <canvas id="realtime">{{ chart }}</canvas>
 <div class="card-content">
  <h3>Realtime</h3>
  <p>Esta gráfica muestra información en tiempo real</p>
 </div>
</div>

Y vamos a abrir el archivo app.component.css y ponemos el siguiente código:

@import url('https://fonts.googleapis.com/css?family=Roboto:300,400');
.card {
 width: 50%;
 display: block;
 background-color: #333941;
 box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
 position: relative;
 min-height: 450px;
 border-radius: 5px;
 margin-bottom: 1rem;
 border: none;
 overflow: hidden;
 color: #0a0a0a;
}
.card-content {
 background: #fff;
 border-top: solid 1px #cdcdcd;
 padding: 5px 20px;
}
.card h3,
.card p {
 font-family: 'Roboto', sans-serif;
}
.card p {
 font-weight: 300;
}

Aquí tenemos el resultado:

¿Cool no es así?. Si te gustó el tutorial, no te olvides de visitarnos para más tutoriales.

Por último te dejo el tutorial en GitHub:

https://github.com/asfo/Graficas-Angular

¡Suerte y nos vemos en el siguiente tutorial!

¿Cuál es tu reacción?
+1
0
+1
0
+1
0
+1
0
+1
0
Total
0
Shares
Publicación anterior

Haciendo el despliegue de una aplicación Angular (Angular-CLI) a producción con Docker y Nginx

Siguiente Publicación

Subiendo miles y miles de archivos a GitHub fácilmente con Git+Bash

Publicaciones Relacionadas