
En este artículo, trataré de cubrir en detalle el alcance limitado de la tecnología dentro del marco del marco angular y su asistente ya integral: RxJs, mientras que no tocaremos deliberadamente las implementaciones del servidor, como Este es un tema completo para un artículo separado.
Este texto será útil para aquellos que ya están familiarizados con Angular, pero desean profundizar sus conocimientos directamente sobre el tema.
Primero, alguna información básica.
¿Qué es WebSocket y por qué lo necesita?
Según Wikipedia , WebSocket es un "protocolo de comunicación dúplex (puede transmitir y recibir al mismo tiempo) a través de una conexión TCP, diseñado para la mensajería en tiempo real entre un navegador y un servidor web.
WebSocket está diseñado para implementarse en navegadores web y servidores web, pero se puede usar para cualquier aplicación cliente o servidor. El protocolo WebSocket es un protocolo independiente basado en el protocolo TCP. Permite una interacción más estrecha entre el navegador y el sitio web, facilitando la distribución de contenido interactivo y la creación de aplicaciones en tiempo real ".
En otras palabras, WebSocket permite que el servidor reciba solicitudes del cliente y envíe solicitudes al cliente en cualquier momento deseado, por lo tanto, el navegador (cliente) y el servidor reciben los mismos derechos y la capacidad de intercambiar mensajes cuando se conecta. Una solicitud AJAX regular requiere la transmisión de encabezados HTTP completos, lo que significa un mayor tráfico en ambas direcciones, mientras que la sobrecarga de los sockets web después de establecer una conexión es de solo dos bytes. El socket web reduce la cantidad de información transmitida en los encabezados HTTP cientos y miles de veces y reduce significativamente el tiempo de espera. Las conexiones de socket web admiten dominios cruzados como CORS.
En el lado del servidor, hay paquetes para admitir el socket web, en el cliente es la API HTML5 WebSocket, que tiene una interfaz de tres métodos:
WebSocket : la interfaz principal para conectarse a un servidor WebSocket y luego enviar y recibir datos a través de la conexión;
CloseEvent : un evento enviado por el objeto WebSocket cuando se cerró la conexión;
MessageEvent : un evento enviado por un WebSocket cuando se recibe un mensaje del servidor.
Así es como se ve en el nivel de implementación de 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 - escucha mensajes del servidor
enviar : envía tus mensajes al servidor
Es decir, en su forma básica, todo es extremadamente simple, pero si desea profundizar en el tema, puede recurrir a
MDN Web Docs y al mismo tiempo estudiar bibliotecas que implementan sus propias capas en la parte superior de esta API.
¿Por qué no tener miedo de usar WebSocket?
El primer punto que puede asustar es el
soporte del navegador . Hoy en día no existe tal problema: WebSocket es compatible casi por completo tanto en la web como en el segmento móvil.

https://caniuse.com/#feat=websocketsEl segundo punto es la
facilidad de implementación . Sí, al principio esto es desalentador.
La API es tan simple que a primera vista puede ser difícil entender cómo trabajar con un número tan modesto de métodos, porque todos, excepto uno, informan errores o conexiones, y solo uno de ellos,
onmessage , lleva para qué sockets web se utilizan, es decir para recibir datos del servidor.
Al mismo tiempo, el problema es que el servidor generalmente envía datos diferentes, por lo tanto, ¿necesitamos varios mensajes diferentes? ¿O necesita crear su propia conexión para cada modelo de datos?
Entonces, la tarea: debe aceptar el modelo de usuario y el último modelo de noticias del servidor, y tal vez incluso algo más.
Me he encontrado con una implementación tan "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) => { // ... };
A primera vista, todo es lógico. Pero ahora imagine cómo se verá si hay docenas o cientos de ellos. En uno de los proyectos en los que trabajé, hubo unos trescientos eventos.

Solucionamos el problema.
Todas las bibliotecas de terceros para trabajar con sockets web le permiten suscribirse a mensajes del tipo addEventListener. Se ve así:
ws.on("user", (userData) => { / .. })
Como sabemos, podemos operar con un solo método: un
mensaje , que recibe todos los datos como parte de su conexión, por lo que este código parece algo inusual. Esto se implementa de la siguiente manera:
onmessage devuelve un
mensaje de evento que contiene el campo de
datos . Son los
datos que contienen la información que el servidor nos envía. Este objeto se ve así:
{ "event": "user", "data": { "name": "John Doe", ... } }
donde
evento es la clave por la cual es posible determinar qué información envió el servidor. A continuación, en el lado frontal, se crea un bus que filtra la información por evento y la envía a la dirección deseada:
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') { // ... } };
Esto permite recibir datos diferentes dentro de la misma conexión y suscribirse a ellos a través de una sintaxis similar a la habitual para los eventos JS.
WebSockets en Angular
Finalmente, llegamos a lo más importante: usar WebSockets directamente en Angular.
A pesar de la simplicidad de trabajar con la API nativa de WebSocket, en este artículo usaremos RxJs, lo cual, por supuesto, porque estamos hablando de Angular.
La API nativa de WebSocket se puede usar en aplicaciones en Angular, crear una interfaz fácil de usar sobre la base, RxJs Observable, suscribirse a los mensajes necesarios, etc., pero RxJs ya ha hecho el trabajo principal para usted: WebSocketSubject es un envoltorio reactivo sobre el WebSocket estándar API No crea un bus de eventos ni vuelve a conectar el procesamiento. Este es un tema normal, con el que puede trabajar con sockets web de estilo reactivo.
RxJs WebSocketSubject
Por lo tanto,
WebSocketSubject espera
WebSocketSubjectConfig y un destino opcional, en el que puede pasar un enlace a su Asunto observado, crea un Observable a través del cual puede escuchar y enviar mensajes para enchufes web.
En pocas palabras, pase la url de conexión como argumento a WebSocketSubject y suscríbase a toda la actividad del socket web de la manera habitual para RxJs. Y si necesita enviar un mensaje al servidor, utilice el mismo método habitual webSocketSubject.next (data).
Hacemos un servicio para trabajar con WebSocket Angular
Describa brevemente lo que esperamos del servicio:
- Interfaz unificada y concisa;
- Posibilidad de configuración a nivel de conexión de dependencias DI ;
- La posibilidad de reutilización;
- Mecanografía;
- La capacidad de suscribirse para recibir información por clave;
- Posibilidad de rescindir una suscripción;
- Enviar mensajes al servidor;
- Reconectar
Vale la pena prestar especial atención al último punto. La reconexión, o la organización de reconectarse al servidor, es un factor primordial cuando se trabaja con sockets web, como interrupciones de la red, fallas del servidor u otros errores que causan una interrupción de la conexión pueden hacer que la aplicación se bloquee
Es importante tener en cuenta que los intentos de reconexión
no deberían ser demasiado frecuentes y no deberían continuar indefinidamente, ya que Este comportamiento puede suspender al cliente.
Empecemos
En primer lugar, crearemos una interfaz de configuración de servicio y un módulo que proporcionará la capacidad de configurar cuando esté conectado.
Si es posible, acortaré el código, la versión completa que puedes ver
en la comunidad angular de habla rusa en 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 }] }; } }
A continuación, debemos describir la interfaz del mensaje del sitio web:
export interface IWsMessage<T> { event: string; data: T; }
donde
evento es la clave, y los
datos obtenidos por la clave es un modelo escrito.
La interfaz pública del servicio se ve así:
export interface IWebsocketService { on<T>(event: string): Observable<T>; send(event: string, data: any): void; status: Observable<boolean>; }
El servicio tiene 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>;
En el constructor de la clase de servicio, obtenemos el objeto WebSocketConfig especificado cuando se conectó el módulo:
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(); }
El método de conexión en sí es simple:
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(); } }); }
Reconectarse es un poco más 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(); } }); }
El método
on , también es extremadamente simple, no hay nada que 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) ); } }
El método de
envío es aún más simple:
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!'); } }
Ese es todo el servicio. Como puede ver, la mayor parte del código recayó en la organización de la reconexión.
Veamos ahora cómo usarlo. Conecte el módulo WebsocketModule:
imports: [ WebsocketModule.config({ url: environment.ws // 'ws://www.example.com' }) ]
En el constructor de componentes, inyectamos el servicio y nos suscribimos a los mensajes de '
mensajes ', enviamos el texto de vuelta al servidor:
constructor(private wsService: WebsocketService) { this.wsService.on<IMessage[]>('messages') .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send('text', 'Test Text!'); }); }
El nombre de los eventos es más conveniente para poner en constantes o enumeraciones. Creamos en algún lugar el archivo websocket.events.ts y lo escribimos en él:
export const WS = { ON: { MESSAGES: 'messages' }, SEND: { TEXT: 'text' } };
Reescribe las suscripciones usando el objeto WS creado:
this.wsService.on<IMessage[]>(WS.ON.MESSAGES) .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send(WS.SEND.TEXT, 'Test Text!'); });

En conclusión
Eso, de hecho, es todo. Este es un mínimo necesario que un desarrollador Angular necesita saber sobre WebSockets. Espero haber cubierto este tema con bastante claridad. La versión completa del servicio se puede encontrar en
GitHub .
Para todas las preguntas puede contactarme en los comentarios,
a mí en Telegram o en
el canal Angular en el mismo lugar.