
Neste artigo, tentarei abordar em detalhes o escopo restrito da tecnologia dentro da estrutura da estrutura Angular e seu assistente já integrante - RxJs, enquanto não tocaremos deliberadamente em implementações de servidor, como Este é um tópico completo para um artigo separado.
Este texto será útil para aqueles que já estão familiarizados com o Angular, mas desejam aprofundar seus conhecimentos diretamente sobre o assunto.
Primeiro, algumas informações básicas.
O que é o WebSocket e por que você precisa dele
Segundo a Wikipedia , o WebSocket é um “protocolo de comunicação duplex (pode transmitir e receber ao mesmo tempo) por uma conexão TCP, projetado para mensagens em tempo real entre um navegador e um servidor da web.
O WebSocket foi projetado para ser implementado em navegadores e servidores da Web, mas pode ser usado para qualquer aplicativo cliente ou servidor. O protocolo WebSocket é um protocolo independente baseado no protocolo TCP. Permite uma interação mais próxima entre o navegador e o site, facilitando a distribuição de conteúdo interativo e a criação de aplicativos em tempo real. ”
Em outras palavras, o WebSocket permite que o servidor receba solicitações do cliente e envie solicitações ao cliente a qualquer momento desejado, assim, o navegador (cliente) e o servidor recebem direitos iguais e a capacidade de trocar mensagens ao se conectar. Uma solicitação AJAX regular requer a transmissão de cabeçalhos HTTP completos, o que significa aumento do tráfego nas duas direções, enquanto a sobrecarga dos soquetes da web após o estabelecimento de uma conexão é de apenas dois bytes. O soquete da Web reduz a quantidade de informações transmitidas nos cabeçalhos HTTP em centenas e milhares de vezes e reduz significativamente o tempo de espera. As conexões de soquete da Web oferecem suporte a vários domínios, como o CORS.
No lado do servidor, existem pacotes para suportar o soquete da Web, no cliente é a API WebSocket HTML5, que possui uma interface de três métodos:
WebSocket - a interface principal para conectar-se a um servidor WebSocket e depois enviar e receber dados pela conexão;
CloseEvent - um evento despachado pelo objeto WebSocket quando a conexão foi fechada;
MessageEvent - Um evento despachado por um WebSocket quando uma mensagem é recebida do servidor.
É assim que olha o nível de implementação do JavaSript:
const ws = new WebSocket("ws://www.example.com/socketserver", "protocolOne"); ws.onopen = () => { ws.onmessage = (event) => { console.log(event); } ws.send("Here's some text that the server is urgently awaiting!"); };
onmessage - ouve mensagens do servidor
enviar - envie suas mensagens para o servidor
Ou seja, em sua forma básica, tudo é extremamente simples, mas se você deseja se aprofundar no tópico, pode recorrer ao
MDN Web Docs e ao mesmo tempo estudar bibliotecas que implementam suas próprias camadas na parte superior desta API.
Por que não ter medo de usar o WebSocket
O primeiro ponto que pode assustar é
o suporte ao navegador . Hoje não existe esse problema - o WebSocket é suportado quase completamente na web e no segmento móvel.

https://caniuse.com/#feat=websocketsO segundo ponto é a
facilidade de implementação . Sim, a princípio isso é desanimador.
A API é tão simples que, à primeira vista, pode ser difícil entender como trabalhar com um número tão modesto de métodos, porque todos eles, exceto um, relatam erros ou conexões, e apenas um deles - na
mensagem - carrega para quais soquetes da web são usados, ou seja, para receber dados do servidor.
Ao mesmo tempo, o problema é que o servidor geralmente envia dados diferentes; portanto, precisamos de várias mensagens diferentes? Ou você precisa criar sua própria conexão para cada modelo de dados?
Portanto, a tarefa: você precisa aceitar o modelo do usuário e as últimas notícias do servidor, e talvez até outra coisa.
Eu me deparei com uma implementação tão "elegante":
const wsUser = new WebSocket("ws://www.example.com/user"); wsUser.onmessage = (event) => { // ... }; const wsNews = new WebSocket("ws://www.example.com/news"); wsNews.onmessage = (event) => { // ... }; const wsTime = new WebSocket("ws://www.example.com/time"); wsTime.onmessage = (event) => { // ... }; const wsDinner = new WebSocket("ws://www.example.com/dinner"); wsDinner.onmessage = (event) => { // ... }; const wsCurrency = new WebSocket("ws://www.example.com/currency"); wsCurrency.onmessage = (event) => { // ... }; const wsOnline = new WebSocket("ws://www.example.com/online"); wsOnline.onmessage = (event) => { // ... }; const wsLogin = new WebSocket("ws://www.example.com/login"); wsLogin.onmessage = (event) => { // ... }; const wsLogout = new WebSocket("ws://www.example.com/logout"); wsLogout.onmessage = (event) => { // ... };
À primeira vista, tudo é lógico. Mas agora imagine como ficará se houver dezenas ou centenas deles. Em um dos projetos nos quais eu trabalhei, houve cerca de trezentos eventos.

Nós resolvemos o problema.
Todas as bibliotecas de terceiros para trabalhar com soquetes da Web permitem que você assine mensagens do tipo addEventListener. É assim:
ws.on("user", (userData) => { / .. })
Como sabemos, podemos operar com um único método - na
mensagem , que recebe todos os dados como parte de sua conexão, portanto esse código parece um tanto incomum. Isso é implementado da seguinte maneira:
onmessage retorna um
MessageEvent que contém o campo de
dados . São
dados que contêm as informações que o servidor nos envia. Este objeto fica assim:
{ "event": "user", "data": { "name": "John Doe", ... } }
onde
event é a chave pela qual é possível determinar quais informações o servidor enviou. Em seguida, no lado frontal, é criado um barramento que filtra as informações por evento e as envia para o endereço desejado:
const ws = new WebSocket("ws://www.example.com"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.event === 'user') { // ... } if (data.event === 'news') { // ... } };
Isso possibilita o recebimento de dados diferentes na mesma conexão e a assinatura deles através de uma sintaxe semelhante à usual para eventos JS.
WebSockets em Angular
Finalmente, chegamos ao ponto mais importante - usando WebSockets diretamente no Angular.
Apesar da simplicidade de trabalhar com a API WebSocket nativa, neste artigo usaremos RxJs, o que, é claro, porque estamos falando de Angular.
A API WebSocket nativa pode ser usada em aplicativos Angular, criar uma interface fácil de usar, RxJs Observable, assinar as mensagens necessárias etc., mas RxJs já fez o trabalho principal para você: WebSocketSubject é um invólucro reativo no WebSocket padrão API Ele não cria um barramento de eventos ou reconecta o processamento. Este é um assunto comum com o qual você pode trabalhar com soquetes da Web de estilo reativo.
RxJs WebSocketSubject
Portanto, o
WebSocketSubject espera que o
WebSocketSubjectConfig e um destino opcional, no qual você possa passar um link para o Assunto observado, crie um Observable através do qual você possa ouvir e enviar mensagens para os soquetes da Web.
Simplificando, você passa o URL da conexão como o argumento WebSocketSubject e assina toda a atividade do soquete da Web da maneira usual para RxJs. E se você precisar enviar uma mensagem para o servidor, use o mesmo método usual webSocketSubject.next (data).
Prestamos um serviço para trabalhar com o WebSocket Angular
Descreva brevemente o que esperamos do serviço:
- Interface unificada e concisa;
- Possibilidade de configuração no nível de conexão das dependências de DI ;
- A possibilidade de reutilização;
- Digitação;
- A capacidade de se inscrever para receber informações por chave;
- Capacidade de encerrar uma assinatura;
- Enviando mensagens para o servidor;
- Reconecte.
Vale a pena prestar atenção ao último ponto. A reconexão ou a organização da reconexão com o servidor é um fator primordial ao trabalhar com soquetes da Web, pois quebras de rede, falhas no servidor ou outros erros que causem uma falha na conexão podem causar falhas no aplicativo.
É importante observar que as tentativas de reconexão
não devem ser muito frequentes e não devem continuar indefinidamente, pois esse comportamento pode suspender o cliente.
Vamos começar.
Primeiro, criaremos uma interface de configuração de serviço e um módulo que fornecerá a capacidade de configurar quando conectado.
Se possível, reduzirei o código, a versão completa que você pode ver
na comunidade Angular de língua russa no GitHub .
export interface WebSocketConfig { url: string; reconnectInterval?: number; reconnectAttempts?: number; } export class WebsocketModule { public static config(wsConfig: WebSocketConfig): ModuleWithProviders { return { ngModule: WebsocketModule, providers: [{ provide: config, useValue: wsConfig }] }; } }
Em seguida, precisamos descrever a interface da mensagem do soquete da web:
export interface IWsMessage<T> { event: string; data: T; }
onde
event é a chave e os
dados obtidos pela chave são um modelo digitado.
A interface pública do serviço se parece com isso:
export interface IWebsocketService { on<T>(event: string): Observable<T>; send(event: string, data: any): void; status: Observable<boolean>; }
O serviço possui campos:
// WebSocketSubject private config: WebSocketSubjectConfig<IWsMessage<any>>; private websocketSub: SubscriptionLike; private statusSub: SubscriptionLike; // Observable interval private reconnection$: Observable<number>; private websocket$: WebSocketSubject<IWsMessage<any>>; // , private connection$: Observer<boolean>; // Observable private wsMessages$: Subject<IWsMessage<any>>; // private reconnectInterval: number; // private reconnectAttempts: number; // private isConnected: boolean; // public status: Observable<boolean>;
No construtor da classe de serviço, obtemos o objeto WebSocketConfig especificado quando o módulo foi conectado:
constructor(@Inject(config) private wsConfig: WebSocketConfig) { this.wsMessages$ = new Subject<IWsMessage<any>>(); // , , this.reconnectInterval = wsConfig.reconnectInterval || 5000; this.reconnectAttempts = wsConfig.reconnectAttempts || 10; // connection$ websocket$ this.config = { url: wsConfig.url, closeObserver: { next: (event: CloseEvent) => { this.websocket$ = null; this.connection$.next(false); } }, // connection$ openObserver: { next: (event: Event) => { console.log('WebSocket connected!'); this.connection$.next(true); } } }; // connection status this.status = new Observable<boolean>((observer) => { this.connection$ = observer; }).pipe(share(), distinctUntilChanged()); // this.statusSub = this.status .subscribe((isConnected) => { this.isConnected = isConnected; if (!this.reconnection$ && typeof(isConnected) === 'boolean' && !isConnected) { this.reconnect(); } }); // , - this.websocketSub = this.wsMessages$.subscribe( null, (error: ErrorEvent) => console.error('WebSocket error!', error) ); // this.connect(); }
O próprio método de conexão é simples:
private connect(): void { this.websocket$ = new WebSocketSubject(this.config); // // , , // , // , this.websocket$.subscribe( (message) => this.wsMessages$.next(message), (error: Event) => { if (!this.websocket$) { // run reconnect if errors this.reconnect(); } }); }
Reconectar é um pouco mais complicado:
private reconnect(): void { // interval reconnectInterval this.reconnection$ = interval(this.reconnectInterval) .pipe(takeWhile((v, index) => index < this.reconnectAttempts && !this.websocket$)); // , this.reconnection$.subscribe( () => this.connect(), null, () => { // Subject complete if reconnect attemts ending this.reconnection$ = null; if (!this.websocket$) { this.wsMessages$.complete(); this.connection$.complete(); } }); }
O método
on , também é extremamente simples, não há nada para comentar.
public on<T>(event: string): Observable<T> { if (event) { return this.wsMessages$.pipe( filter((message: IWsMessage<T>) => message.event === event), map((message: IWsMessage<T>) => message.data) ); } }
O método de
envio é ainda mais simples:
public send(event: string, data: any = {}): void { if (event && this.isConnected) { // any , "" string // :) this.websocket$.next(<any>JSON.stringify({ event, data })); } else { console.error('Send error!'); } }
Esse é todo o serviço. Como você pode ver, a maior parte do código recaiu sobre a organização da reconexão.
Vamos agora ver como usá-lo. Conecte o módulo WebsocketModule:
imports: [ WebsocketModule.config({ url: environment.ws // 'ws://www.example.com' }) ]
No construtor de componentes, injetamos o serviço e assinamos mensagens de '
messages ', enviamos o texto de volta ao servidor:
constructor(private wsService: WebsocketService) { this.wsService.on<IMessage[]>('messages') .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send('text', 'Test Text!'); }); }
O nome dos eventos é mais conveniente para colocar constantes ou enumerações. Criamos em algum lugar o arquivo websocket.events.ts e o gravamos nele:
export const WS = { ON: { MESSAGES: 'messages' }, SEND: { TEXT: 'text' } };
Reescreva assinaturas usando o objeto WS criado:
this.wsService.on<IMessage[]>(WS.ON.MESSAGES) .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send(WS.SEND.TEXT, 'Test Text!'); });

Em conclusão
Isso, de fato, é tudo. Esse é o mínimo necessário que um desenvolvedor Angular precisa saber sobre WebSockets. Espero ter coberto esse tópico com muita clareza. A versão completa do serviço pode ser encontrada no
GitHub .
Para todas as perguntas, você pode entrar em contato nos comentários, no Telegram ou no
canal Angular, no mesmo local.