CodeForge

Angular 21 de Cero a Experto / Tiempo real y sistemas distribuidos

WebSocket nativo: los cuatro momentos de una conexión

Teoría22 min20 XP

El navegador trae una API WebSocket nativa: una conexión persistente y bidireccional con el servidor. Entenderla a fondo —sus cuatro momentos, cómo enviar y recibir, sus límites— es la base antes de usar librerías como Socket.io. Hoy abres tu primera conexión WebSocket, la integras con señales para que el template reaccione, y ves por qué en producción conviene una capa por encima.

Una línea telefónica abierta

Los cuatro momentos

Una conexión WebSocket tiene cuatro eventos que debes manejar:

websocket.ts
const ws = new WebSocket('wss://api.mibolsillo.com/tiempo-real');

// 1. OPEN: la conexión se estableció (la línea está abierta)
ws.onopen = () => {
console.log('conectado');
ws.send(JSON.stringify({ tipo: 'suscribir', canal: 'gastos' }));
};

// 2. MESSAGE: llegó un mensaje del servidor
ws.onmessage = (evento) => {
const mensaje = JSON.parse(evento.data);
console.log('llegó', mensaje);
};

// 3. ERROR: algo falló en la conexión
ws.onerror = (evento) => console.error('error de conexión', evento);

// 4. CLOSE: la conexión se cerró (colgaron)
ws.onclose = (evento) => console.log('desconectado', evento.code);
  • onopen — la conexión está lista; aquí sueles suscribirte a canales o enviar un saludo.
  • onmessage — cada mensaje entrante; evento.data es texto (parseas el JSON).
  • onerror — un problema de red o del servidor.
  • onclose — la conexión terminó (con un code que indica por qué); aquí decides si reconectar.

Enviar y recibir con tipos

Como en cualquier comunicación, conviene un "idioma" claro: mensajes con un campo tipo que los discrimina —la misma discriminated union del SC-03—:

mensajes.ts
// el vocabulario de mensajes, tipado
type MensajeServidor =
| { tipo: 'gasto_nuevo'; gasto: Gasto }
| { tipo: 'gasto_borrado'; id: number }
| { tipo: 'presupuesto'; valor: number };

ws.onmessage = (evento) => {
const msg: MensajeServidor = JSON.parse(evento.data);
switch (msg.tipo) {                     // el switch estrecha el tipo
  case 'gasto_nuevo':    this.agregarGasto(msg.gasto); break;
  case 'gasto_borrado':  this.quitarGasto(msg.id); break;
  case 'presupuesto':    this.presupuesto.set(msg.valor); break;
}
};

// enviar es simétrico: serializas y mandas
ws.send(JSON.stringify({ tipo: 'crear_gasto', nombre: 'Café', valor: 4500 }));

El campo tipo + switch es el mismo patrón que usaste para acciones de reducer y estados de carga: hace el protocolo de mensajes robusto y exhaustivo. Serializas con JSON.stringify al enviar y parseas con JSON.parse al recibir.

Integrarlo con señales

Para que tu UI reaccione, vuelca los mensajes entrantes en una señal:

tiempo-real.service.ts
@Injectable({ providedIn: 'root' })
export class TiempoRealService {
private ws?: WebSocket;
readonly gastos = signal<Gasto[]>([]);
readonly conectado = signal(false);

conectar() {
  this.ws = new WebSocket('wss://api.mibolsillo.com/rt');
  this.ws.onopen = () => this.conectado.set(true);
  this.ws.onclose = () => this.conectado.set(false);
  this.ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.tipo === 'gasto_nuevo') {
      this.gastos.update((gs) => [...gs, msg.gasto]);   // el template se repinta solo
    }
  };
}

enviar(datos: unknown) { this.ws?.send(JSON.stringify(datos)); }
desconectar() { this.ws?.close(); }
}

Los mensajes entrantes actualizan la señal gastos, y cualquier componente que la lea se actualiza al instante. La señal conectado te deja mostrar un indicador de estado. Este es el puente natural entre el WebSocket (eventos en el tiempo) y las señales (estado que la UI lee).

Los límites del WebSocket nativo

¿Qué NO hace por ti la API `WebSocket` nativa del navegador?

Mini-reto

Diseña el servicio de tiempo real de "Mi Bolsillo" con WebSocket nativo: 1) una discriminated union MensajeServidor con gasto_nuevo, gasto_borrado y usuario_conectado; 2) el onmessage con un switch exhaustivo que actualice las señales correspondientes; 3) señales conectado y gastos. Marca en qué punto necesitarías lógica de reconexión y por qué el nativo no la trae.

Qué sigue

Conoces el WebSocket crudo y sus límites. La próxima lección sube al nivel de producción con Socket.io: reconexión automática, eventos con nombre y salas —todo lo que el nativo no trae—, integrado en Angular con un socket que construirás y probarás en vivo.