CodeForge

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

Dashboard en vivo y presencia

Teoría20 min20 XP

Con la conexión robusta, vamos a lo vistoso: un DASHBOARD que agrega en vivo los datos que llegan por el socket (totales, por categoría, últimos movimientos) y la PRESENCIA —quién está conectado ahora mismo—. Son las dos experiencias que hacen que una app se sienta "viva". Hoy las construyes con señales y computed, y lo pruebas en vivo. La antesala directa del reto y del proyecto final.

El tablero que se pinta solo

Agregar datos en vivo con computed

El patrón es directo: los eventos del socket alimentan una señal, y los computed derivan las métricas del dashboard —que se recalculan solas con cada evento—:

dashboard.service.ts
@Injectable({ providedIn: 'root' })
export class DashboardService {
// los gastos que llegan en vivo por el socket
private readonly _gastos = signal<Gasto[]>([]);
readonly gastos = this._gastos.asReadonly();

// MÉTRICAS derivadas: se recalculan solas con cada evento entrante
readonly total = computed(() => this._gastos().reduce((s, g) => s + g.valor, 0));
readonly porCategoria = computed(() =>
  this._gastos().reduce((acc, g) => {
    acc[g.categoria] = (acc[g.categoria] ?? 0) + g.valor;
    return acc;
  }, {} as Record<string, number>));
readonly ultimos = computed(() => this._gastos().slice(-5).reverse());
readonly promedioGasto = computed(() =>
  this._gastos().length ? Math.round(this.total() / this._gastos().length) : 0);

// el socket empuja → actualizamos la señal → todas las métricas reaccionan
alLlegarGasto(g: Gasto) {
  this._gastos.update((gs) => [...gs, g]);
}
}

Toda la potencia de las señales al servicio del tiempo real: un solo update cuando llega un gasto, y total, porCategoria, ultimos y promedioGasto se recalculan y la UI se repinta. No escribes ni una línea de "actualizar el dashboard": los computed lo hacen.

Presencia: quién está conectado

La presencia se modela con eventos de entrada/salida que mantienen un conjunto de usuarios:

presencia.service.ts
@Injectable({ providedIn: 'root' })
export class PresenciaService {
private readonly _conectados = signal<Map<string, string>>(new Map());  // id → nombre
readonly conectados = computed(() => [...this._conectados().values()]);
readonly cuantos = computed(() => this._conectados().size);

// el socket avisa quién entra y quién sale
usuarioEntra(id: string, nombre: string) {
  this._conectados.update((m) => new Map(m).set(id, nombre));
}
usuarioSale(id: string) {
  this._conectados.update((m) => { const n = new Map(m); n.delete(id); return n; });
}
}
// en el socket: socket.on('usuario:entra', (u) => presencia.usuarioEntra(u.id, u.nombre))
//               socket.on('usuario:sale',  (u) => presencia.usuarioSale(u.id))

Un detalle: al actualizar el Map, se crea uno NUEVO (new Map(m)) —inmutabilidad, para que la señal detecte el cambio—. Los computed derivan la lista y el conteo, y la UI muestra "3 en línea" y sus nombres, actualizándose al instante cuando alguien entra o sale.

"Escribiendo…" con throttle

El indicador de "está escribiendo" es un caso clásico: se emite MUCHO (en cada tecla), así que se limita con throttle para no saturar:

escribiendo.ts
// al escribir, emites "escribiendo" —pero con throttle para no mandar en cada tecla—
this.input.valueChanges.pipe(
throttleTime(1000),   // como mucho un aviso por segundo
).subscribe(() => this.socket.emit('escribiendo', { usuario: this.miId }));

// y en el otro lado, muestras el aviso y lo ocultas tras un rato sin recibirlo

Pruébalo: un dashboard en vivo, corriendo

Un "servidor" empuja gastos y eventos de presencia; el dashboard agrega todo en vivo. Fíjate en que las métricas nunca se calculan a mano —se derivan—:

Observa: cada evento del "servidor" (gasto o presencia) actualiza el estado, y las métricas —total, promedio, por categoría, conectados— se recalculan solas en el render. En Angular real, esas métricas serían computed sobre señales, y el template se repintaría sin intervención. Un dashboard en vivo es exactamente esto: estado que llega por el socket + derivados reactivos.

En el dashboard, ¿por qué el total y las métricas por categoría se implementan como `computed` y no se actualizan a mano en cada evento del socket?

Mini-reto

En el playground, amplía el dashboard: 1) una métrica categoriaTop (la categoría con más gasto acumulado); 2) un contador de movimientosPorMinuto (cuántos gastos llegaron); 3) una lista de "últimos 3 movimientos". Recuerda: todas deben DERIVARSE del estado, no actualizarse a mano. Piensa cómo serían como computed en el servicio Angular.

Qué sigue

Tienes todas las piezas del tiempo real: conexión robusta, eventos, presencia y dashboard en vivo. Es hora del reto del módulo: construir un panel colaborativo en tiempo real completo —con socket simulado, presencia y métricas en vivo—, el ensayo directo del proyecto final del curso.