Ir para o conteúdo
Grade de Angular de Alto Desempenho com Soquetes Web

Grade de Angular de Alto Desempenho com Soquetes Web

Você pode ter se deparado com o requisito de enviar dados em tempo real para uma grade Angular. Para enviar dados para o navegador, você precisa de uma tecnologia chamada WebSocket. Você pode implementar isso usando NodeJS ou ASP.NET SignalR. Para os fins deste artigo, usaremos Web Sockets com NodeJS.

10min read

Você pode ter se deparado com o requisito de enviar dados em tempo real para uma grade Angular. Para enviar dados para o navegador, você precisa de uma tecnologia chamada WebSocket. Você pode implementar isso usando NodeJS ou ASP.NET SignalR. Para os fins deste artigo, usaremos Web Sockets com NodeJS.

Na primeira metade deste artigo, criaremos uma API que usará Web Sockets para enviar dados ao cliente e, na segunda metade do artigo, criaremos uma aplicação Angular para consumir isso. Na Angular aplicação, usaremos Ignite UI for Angular Grid.  No entanto, você também pode usar uma tabela HTML simples para consumir dados em tempo real a partir do web socket. Neste artigo, aprenderemos a consumir dados em tempo real do NodeJS Web Socket em uma tabela HTML, assim como Ignite UI Angular Data Grid. Vamos testemunhar diferenças de desempenho entre essas duas abordagens.

Você pode aprender mais sobre Ignite UI for Angular.

NodeJS API

Vamos começar criando a API NodeJS. Crie uma pasta em branco e adicione um arquivo chamado package.json. Em package.json, adicione dependências de

  • Core-js
  • Express
  • io

Mais ou menos, seu arquivo package.json deve ficar como está abaixo:

{
  "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"
}

Você pode extrair dados de qualquer tipo de banco de dados, como banco de dados relacional, banco de dados sem SQL, etc. No entanto, para os fins deste post, vou manter simples e ter os dados codificados diretamente no arquivo data.js. Esse arquivo exporta um array JSON, que vamos lançar usando web socket e timer.

Adicione um arquivo em uma pasta chamada data.js e adicione o seguinte código nela.

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 
    ]
}

Você pode encontrar dados com 1200 linhas aqui.

A partir data.js arquivo, estamos retornando dados do TradeBlotter. Agora, na pasta do seu projeto, você deve ter dois arquivos: package.json e data.js

Neste momento, execute o comando npm install para instalar todas as dependências mencionadas em package.json arquivo. Depois de executar o comando, você terá a pasta node_modules na sua pasta do projeto.  Além disso, adicione server.js arquivo no projeto.  Após todos esses passos, a estrutura do seu projeto deve ter os seguintes arquivos e pastas.

  • js
  • js
  • Node_modules folder

Em server.js, começaremos primeiro importando os módulos necessários,

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

Uma vez importados os módulos necessários, adicione o expresso usando rota conforme abaixo:

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

Ao conectar a tomada, estamos realizando as seguintes tarefas:

  1. Fetching data
  2. Temporizador inicial (Falaremos sobre essa função mais adiante no post)
  3. Ao evento de desconexão deletando o 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}`);
    });
 
});

Em seguida, temos que implementar a função startTimer(). Nessa função, estamos usando a função setInterval() do JavaScript e emitindo dados a cada intervalo de 10 milissegundos.

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 chamando uma função updateData() que atualizará os dados. Nessa função, estamos percorrendo dados locais e atualizando duas propriedades, Cupon e Notional, com número aleatório entre os intervalos.

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

Ao juntar tudo, sever.js deve ter o código seguinte

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

Criamos Web Sockets no NodeJS, que retornam blocos de dados a cada 10 milissegundos.

Creating Angular Application

Neste passo, vamos criar Angular aplicação. Vamos usar Angular CLI para criar uma aplicação e depois adicionar Ignite UI for Angular Grid. Siga o artigo abaixo para criar um aplicativo Angular e adicionar Ignite UI for Angular Grade na aplicação.

Se você está seguindo o artigo acima, precisa de mudanças na terceira etapa, na qual estamos criando Angular serviço para consumir API.

Vamos começar instalando o socket.io-client em Angular projeto. Para isso, execute o npm install,

npm i socket.io-client

Vamos escrever um serviço Angular para criar a conexão com o NodeJS Web Socket. Em app.service.ts, vamos começar pela importação.

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';

Importamos módulos obrigatórios. Mais adiante veremos como o tipo de socket é definido dentro interface.ts arquivo. Em seguida, vamos criar conexão com o Web Socket e buscar os próximos dados da resposta. Antes de retornar o próximo pedaço de dados do socket web, estamos convertendo isso em um Observável.

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

As duas funções acima farão conexão com o web socket, buscam o bloco de dados e convertem isso em observável. Juntando tudo, app.service.ts ficará assim:

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

No serviço, estamos usando um tipo chamado Socket. Criamos este tipo de arquivo interfaces.ts conforme segue:

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

Agora Angular serviço está pronto, que fará conexão com o soquete Web NodeJS e buscará dados da API como observáveis.

Isso é normal Angular serviço e pode ser consumido em um componente da maneira usual. Comece importando isso no módulo e depois injetando isso no construtor de componentes, como mostrado abaixo:

constructor(private dataService: AppService) { }

podemos chamar o método de serviço para buscar dados no ciclo de vida do OnInit,

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

Juntando tudo, a classe componente ficará como a seguinte.

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

Uma coisa importante que você pode querer notar é que estamos cancelando a subscrição do observável retornado no gancho do ciclo de vida OnDestroy do componente.  No modelo, basta renderizar os dados na tabela abaixo:

<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 dados em tempo real em uma tabela HTML normal, você pode ter um piscar e algum problema de desempenho. Vamos substituir tabela HTML por Ignite UI for Angular grade.

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

Você pode adicionar Ignite UI Grade em Angular aplicação, como mostrado abaixo. Definimos a fonte de dados do igxGrid usando data property binding e depois adicionamos manualmente colunas à grade.

<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>

Alguns pontos em que você deve focar na grade que criamos:

  1. Por padrão, a virtualização é ativada na grade Ignite UI for Angular.
  2. Ao definir a propriedade ordenável, você pode ativar a ordenação na coluna específica.
  3. Ao definir a propriedade fixada, você pode fixar uma coluna à esquerda da grade.
  4. Ao definir a propriedade de dados, você pode definir a fonte de dados da grade.
  5. Você pode adicionar colunas manualmente usando <igx-column/>.
  6. O campo e o cabeçalho de <igx-column/> são usados para definir a propriedade do campo e o cabeçalho da coluna.

Agora, ao rodar o aplicativo, você verá que a grade está atualizando em tempo real com os dados e também não piscando. Você vai perceber que a grade está atualizando a cada 10 milissegundos. Você deve ter a grade rodando com dados sendo atualizados em tempo real, como mostrado abaixo:

A grade está funcionando com dados sendo atualizados em tempo real, como mostrado

Dessa forma, você pode enviar dados em tempo real usando a API Web Socket do NodeJS em Angular aplicação. Espero que você ache este artigo útil. Se você gostou deste post, por favor, compartilhe. Além disso, se você ainda não conferiu Infragistics Ignite UI for Angular Components, não deixe de conferir! Eles possuem 50+ componentes de Angular baseados em materiais para ajudar você a programar aplicativos web mais rápido.

Solicite uma demonstração