Angular 21 de Cero a Experto / Tiempo real y sistemas distribuidos
Dashboard en vivo y presencia
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—:
@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:
@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:
// 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 recibirloPrué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.