Saltar al contenido
Red de Angular de alto rendimiento con sockets web

Red de Angular de alto rendimiento con sockets web

Es posible que se haya encontrado con la necesidad de enviar datos en tiempo real a una cuadrícula Angular. Para enviar datos al navegador, necesita una tecnología llamada WebSocket. Puede implementarlo mediante NodeJS o ASP.NET SignalR. A los efectos de este artículo, usaremos Web Sockets con NodeJS.

10min read

Es posible que se haya encontrado con la necesidad de enviar datos en tiempo real a una cuadrícula Angular. Para enviar datos al navegador, necesita una tecnología llamada WebSocket. Puede implementarlo mediante NodeJS o ASP.NET SignalR. A los efectos de este artículo, usaremos Web Sockets con NodeJS.

En la primera mitad de este artículo, crearemos una API que usará Web Sockets para enviar datos al cliente y, en la segunda mitad del artículo, crearemos una aplicación Angular para consumirlos. En la Angular aplicación, utilizaremos Ignite UI for Angular Grid.  Sin embargo, también puedes usar una tabla HTML sencilla para consumir datos en tiempo real desde web socket. En este artículo, aprenderemos a consumir datos en tiempo real desde NodeJS Web Socket en una tabla HTML, así como Ignite UI Angular Data Grid. Veremos diferencias en el rendimiento entre estos dos enfoques.

Puedes aprender más sobre Ignite UI for Angular.

NodeJS API

Empecemos creando la API de NodeJS. Crea una carpeta en blanco y añade un archivo llamado package.json. En package.json, añadir dependencias de

  • Core-js
  • Express
  • io

Más o menos tu archivo de package.json debería verse a continuación:

{
  "name": "demo1",
  "version": "1.0.0",
  "description": "nodejs web socket demo",
  "main": "server.js",
  "dependencies": {
    "core-js": "^2.4.1",
    "express": "^4.16.2",
    "socket.io": "^2.0.4"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Dhananjay Kumar",
  "license": "ISC"
}

Puedes extraer datos de cualquier tipo de base de datos, como bases de datos relacionales, bases de datos sin SQL, etc. Sin embargo, para este post, voy a mantenerlo simple y tener datos codificados en el archivo data.js. Este archivo exportará un array JSON, que enviaremos usando web socket y temporizador.

Añade un archivo en una carpeta llamada data.js y añade el siguiente código en él.

data.js

module.exports = {
    data: TradeBlotterCDS()
};
 
function TradeBlotterCDS() {
    return [
        {
            "TradeId": "1",
            "TradeDate": "11/02/2016",
            "BuySell": "Sell",
            "Notional": "50000000",
            "Coupon": "500",
            "Currency": "EUR",
            "ReferenceEntity": "Linde Aktiengesellschaft",
            "Ticker": "LINDE",
            "ShortName": "Linde AG",
            "Counterparty": "MUFJ",
            "MaturityDate": "20/03/2023",
            "EffectiveDate": "12/02/2016",
            "Tenor": "7",
            "RedEntityCode": "DI537C",
            "EntityCusip": "D50348",
            "EntityType": "Corp",
            "Jurisdiction": "Germany",
            "Sector": "Basic Materials",
            "Trader": "Yael Rich",
            "Status": "Pending"
        }
        // ... other rows of data 
    ]
}

Puedes encontrar datos con 1200 filas aquí.

De data.js archivo, estamos devolviendo datos de TradeBlottter. Ahora, en la carpeta de tu proyecto, deberías tener dos archivos: package.json y data.js

En este momento, ejecuta el comando npm install para instalar todas las dependencias mencionadas en package.json archivo. Después de ejecutar el comando, tendrás la carpeta node_modules en la carpeta del proyecto.  Además, añade server.js archivo en el proyecto.  Después de todos estos pasos, la estructura de tu proyecto debería incluir los siguientes archivos y carpetas.

  • js
  • js
  • Node_modules folder

En server.js, comenzaremos primero importando los módulos requeridos,

const express = require('express'),
    app = express(),
    server = require('http').createServer(app);
io = require('socket.io')(server);
let timerId = null,
    sockets = new Set();
var tradedata = require('./data');

Una vez importados los módulos requeridos, añade exprés usando ruta como se indica a continuación:

app.use(express.static(__dirname + '/dist'));

Al conectar el enchufe, realizamos las siguientes tareas:

  1. Fetching data
  2. Iniciando el temporizador (hablaremos de esta función más adelante en la publicación)
  3. Al evento de desconexión que elimina el socket
io.on('connection', socket => {
 
    console.log(`Socket ${socket.id} added`);
    localdata = tradedata.data;
    sockets.add(socket);
    if (!timerId) {
        startTimer();
    }
    socket.on('clientdata', data => {
        console.log(data);
    });
    socket.on('disconnect', () => {
        console.log(`Deleting socket: ${socket.id}`);
        sockets.delete(socket);
        console.log(`Remaining sockets: ${sockets.size}`);
    });
 
});

A continuación, tenemos que implementar la función startTimer(). En esta función, utilizamos la función setInterval() de JavaScript y emitimos datos en cada intervalo de 10 milisegundos.

function startTimer() {
    timerId = setInterval(() => {
        if (!sockets.size) {
            clearInterval(timerId);
            timerId = null;
            console.log(`Timer stopped`);
        }
        updateData();
        for (const s of sockets) {
            s.emit('data', { data: localdata });
        }
 
    }, 10);
}

Estamos llamando a una función updateData() que actualizará los datos. En esta función, estamos recorriendo datos locales y actualizando dos propiedades, Cupon y Notional, con número aleatorio entre rangos.

function updateData() {
    localdata.forEach(
        (a) => {
            a.Coupon = getRandomInt(10, 500);
            a.Notional = getRandomInt(1000000, 7000000);
        });
}

We have implemented getRandomInit function as shown below:

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
}

Al juntar todo, sever.js debería tener el siguiente código

Server.js

const express = require('express'),
    app = express(),
    server = require('http').createServer(app);
io = require('socket.io')(server);
let timerId = null,
    sockets = new Set();
var tradedata = require('./data');
 
var localdata;
 
app.use(express.static(__dirname + '/dist'));
 
io.on('connection', socket => {
 
    console.log(`Socket ${socket.id} added`);
    localdata = tradedata.data;
    sockets.add(socket);
    if (!timerId) {
        startTimer();
    }
    socket.on('clientdata', data => {
        console.log(data);
    });
    socket.on('disconnect', () => {
        console.log(`Deleting socket: ${socket.id}`);
        sockets.delete(socket);
        console.log(`Remaining sockets: ${sockets.size}`);
    });
 
});
 
function startTimer() {
    timerId = setInterval(() => {
        if (!sockets.size) {
            clearInterval(timerId);
            timerId = null;
            console.log(`Timer stopped`);
        }
        updateData();
        for (const s of sockets) {
            s.emit('data', { data: localdata });
        }
 
    }, 10);
}
 
function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
}
 
function updateData() {
    localdata.forEach(
        (a) => {
            a.Coupon = getRandomInt(10, 500);
            a.Notional = getRandomInt(1000000, 7000000);
        });
}
 
server.listen(8080);
console.log('Visit http://localhost:8080 in your browser');

Hemos creado Web Sockets en NodeJS, que devuelve fragmentos de datos cada 10 milisegundos.

Creating Angular Application

En este paso, creemos Angular aplicación. Vamos a usar Angular CLI para crear una aplicación y luego añadir Ignite UI for Angular Grid. Sigue el artículo siguiente para crear una aplicación Angular y añadir Ignite UI for Angular Grid en la aplicación.

Si sigues el artículo anterior, necesitas cambios en el paso tres, en el que estamos creando Angular servicio para consumir API.

Empecemos instalando socket.io-client en Angular proyecto. Para ello, ejecuta la instalación de npm,

npm i socket.io-client

Escribiremos un servicio Angular para crear la conexión con NodeJS Web Socket. En app.service.ts, empecemos por la importación.

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { map, catchError } from 'rxjs/operators';
import * as socketIo from 'socket.io-client';
import { Socket } from './interfaces';

Hemos importado los módulos requeridos. Más adelante veremos cómo se define el tipo de socket dentro de interface.ts archivo. A continuación, creemos la conexión a Web Socket y obtengamos los siguientes datos de la respuesta. Antes de devolver el siguiente fragmento de datos desde el socket web, lo estamos convirtiendo a un Observable.

getQuotes(): Observable < any > {
    this.socket = socketIo('http://localhost:8080');
    this.socket.on('data', (res) => {
        this.observer.next(res.data);
    });
    return this.createObservable();
}
 
createObservable(): Observable < any > {
    return new Observable<any>(observer => {
        this.observer = observer;
    });
}

Las dos funciones anteriores harán conexión con web socket, obtendrán fragmentos de datos y convertirán ese fragmento en observable. Juntando todo, app.service.ts quedará así:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { map, catchError } from 'rxjs/operators';
import * as socketIo from 'socket.io-client';
import { Socket } from './interfaces';
 
@Injectable()
export class AppService {
 
    socket: Socket;
    observer: Observer<any>;
 
    getQuotes(): Observable<any> {
        this.socket = socketIo('http://localhost:8080');
        this.socket.on('data', (res) => {
            this.observer.next(res.data);
        });
        return this.createObservable();
    }
 
    createObservable(): Observable<any> {
        return new Observable<any>(observer => {
            this.observer = observer;
        });
    }
 
    private handleError(error) {
        console.error('server error:', error);
        if (error.error instanceof Error) {
            let errMessage = error.error.message;
            return Observable.throw(errMessage);
        }
        return Observable.throw(error || 'Socket.io server error');
    }
 
}

En el servicio, estamos usando un tipo llamado Socket. Creamos este tipo de archivo interfaces.ts como sigue:

export interface Socket {
    on(event: string, callback: (data: any) => void);
    emit(event: string, data: any);
}

Ahora Angular servicio está listo, lo que hará conexión con el socket web de NodeJS y obtendrá datos de la API como observables.

Esto es normal Angular servicio y puede consumirse en un componente de la manera habitual. Empieza importando eso en el módulo e inyectándolo en el constructor de componentes como se muestra a continuación:

constructor(private dataService: AppService) { }

podemos llamar al método de servicio para obtener datos en el ciclo de vida de OnInit,

ngOnInit() {
    this.sub = this.dataService.getQuotes()
        .subscribe(quote => {
            this.stockQuote = quote;
            console.log(this.stockQuote);
        });
}

Juntando todo, la clase de componentes se verá como la siguiente.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { AppService } from './app.service';
import { Subscription } from 'rxjs/Subscription';
 
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit, OnDestroy {
 
    stockQuote: number;
    sub: Subscription;
    columns: number;
    rows: number;
    selectedTicker: string;
 
    constructor(private dataService: AppService) { }
 
    ngOnInit() {
        this.sub = this.dataService.getQuotes()
            .subscribe(quote => {
                this.stockQuote = quote;
                console.log(this.stockQuote);
            });
    }
    ngOnDestroy() {
        this.sub.unsubscribe();
    }
}

Una cosa importante que quizá quieras notar es que estamos dando de baja el observable que devuelve en el gancho de ciclo de vida de OnDestroy del componente.  En la plantilla, simplemente renderiza los datos en la tabla como se indica a continuación:

<table>
    <tr *ngFor="let f of stockQuote">
        <td>{{f.TradeId}}</td>
        <td>{{f.TradeDate}}</td>
        <td>{{f.BuySell}}</td>
        <td>{{f.Notional}}</td>
        <td>{{f.Coupon}}</td>
        <td>{{f.Currency}}</td>
        <td>{{f.ReferenceEntity}}</td>
        <td>{{f.Ticker}}</td>
        <td>{{f.ShortName}}</td>
    </tr>
</table>

Como estamos renderizando datos en tiempo real en una tabla HTML normal, puede que experimentes parpadeo y algún problema de rendimiento. Sustituyamos la tabla HTML por Ignite UI for Angular cuadrícula.

Learn more about Ignite UI for Angular Grid here: https://es.infragistics.com/products/ignite-ui-angular/angular/components/grid.html.

Puedes añadir Ignite UI Grid en Angular aplicación como se muestra a continuación. Hemos establecido la fuente de datos de igxGrid usando data property binding y luego añadiendo columnas manualmente a la grid.

<igx-grid [width]="'1172px'" #grid1 id="grid1" [rowHeight]="30" [data]="stockQuote"
          [height]="'600px'" [autoGenerate]="false">
    <igx-column [pinned]="true" [sortable]="true" width="50px" field="TradeId" header="Trade Id" [dataType]="'number'"> </igx-column>
    <igx-column [sortable]="true" width="120px" field="TradeDate" header="Trade Date" dataType="string"></igx-column>
    <igx-column width="70px" field="BuySell" header="Buy Sell" dataType="string"></igx-column>
    <igx-column [sortable]="true" [dataType]="'number'" width="110px" field="Notional" header="Notional">
    </igx-column>
    <igx-column width="120px" [sortable]="true" field="Coupon" header="Coupon" dataType="number"></igx-column>
    <igx-column [sortable]="true" width="100px" field="Price" header="Price" dataType="number">
    </igx-column>
    <igx-column width="100px" field="Currency" header="Currency" dataType="string"></igx-column>
    <igx-column width="350px" field="ReferenceEntity" header="Reference Entity" dataType="string"></igx-column>
    <igx-column [sortable]="true" [pinned]="true" width="130px" field="Ticker" header="Ticker" dataType="string"></igx-column>
    <igx-column width="350px" field="ShortName" header="Short Name" dataType="string"></igx-column>
</igx-grid>

Algunos puntos en los que deberías centrarte en la cuadrícula que creamos:

  1. Por defecto, la virtualización está habilitada en la red de Ignite UI for Angular.
  2. Al configurar la propiedad ordenable, puedes habilitar la ordenación en la columna concreta.
  3. Al establecer la propiedad de fijado, puedes fijar una columna a la izquierda de la cuadrícula.
  4. Al establecer la propiedad de datos, puedes establecer la fuente de datos de la cuadrícula.
  5. Puedes añadir columnas manualmente usando <igx-column/>.
  6. El campo y encabezado de <igx-column/> se utiliza para establecer la propiedad del campo y el encabezado de la columna.

Ahora, cuando ejecutas la aplicación, verás que grid se actualiza en tiempo real con datos y tampoco parpadea. Verás que la cuadrícula se actualiza cada 10 milisegundos. Deberías tener la red funcionando con datos actualizándose en tiempo real, como se muestra a continuación:

En cuadrícula funcionando con datos actualizándose en tiempo real como se muestra

De este modo, puedes enviar datos en tiempo real usando la API NodeJS Web Socket en Angular aplicación. Espero que este artículo te resulte útil. Si te ha gustado esta publicación, compártela. Además, si no has echado un vistazo a Infragistics Ignite UI for Angular Components, ¡asegúrate de hacerlo! Tienen 50+ componentes de Angular basados en materiales para ayudarte a programar aplicaciones web más rápido.

Solicitar una demostración