💻 Exemplos: Capítulo 10: Fundamentos de RxJS: Observables, Observers e Subjects
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 10 — Streams Reativos com RxJS, BehaviorSubject e takeUntilDestroyed()
Este exemplo apresenta os fundamentos da programação reativa assíncrona com RxJS no Angular 18+, utilizando BehaviorSubject, renderização declarativa com o AsyncPipe e cancelamento automático de subscrições em tempo de destruição com takeUntilDestroyed().
// src/app/components/network-status-monitor.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule, AsyncPipe } from '@angular/common';
import { BehaviorSubject, interval, Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
interface NetworkMetric {
latencyMs: number;
packetLossRate: number;
status: 'OPTIMAL' | 'DEGRADED' | 'DISCONNECTED';
}
@Component({
selector: 'app-network-status-monitor',
standalone: true,
imports: [CommonModule, AsyncPipe],
template: `
<div class="max-w-xl mx-auto p-6 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-6">
<div class="flex items-center justify-between border-b pb-4">
<div>
<h2 class="text-xl font-bold text-slate-800">📡 Monitor de Conectividade RxJS</h2>
<p class="text-xs text-slate-500">Streams com BehaviorSubject e AsyncPipe</p>
</div>
<div class="flex gap-2">
<button
type="button"
(click)="simulateDegradation()"
class="text-xs px-3 py-1.5 bg-amber-50 text-amber-800 border border-amber-200 rounded-lg font-semibold hover:bg-amber-100"
>
Simular Degradação
</button>
<button
type="button"
(click)="resetOptimal()"
class="text-xs px-3 py-1.5 bg-emerald-50 text-emerald-800 border border-emerald-200 rounded-lg font-semibold hover:bg-emerald-100"
>
Restaurar Ótimo
</button>
</div>
</div>
<!-- CONSUMO DECLARATIVO DO OBSERVABLE COM ASYNCPIPE -->
@if (metric$ | async; as metric) {
<div class="grid grid-cols-3 gap-3">
<div class="p-4 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">Latência TCP</span>
<div class="text-xl font-black text-slate-900 mt-1">{{ metric.latencyMs }} ms</div>
</div>
<div class="p-4 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">Perda de Pacotes</span>
<div class="text-xl font-black text-slate-900 mt-1">
{{ (metric.packetLossRate * 100).toFixed(1) }}%
</div>
</div>
<div class="p-4 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">Qualidade da Rede</span>
<div
class="text-base font-black mt-1 uppercase"
[ngClass]="{
'text-emerald-600': metric.status === 'OPTIMAL',
'text-amber-600': metric.status === 'DEGRADED',
'text-red-600': metric.status === 'DISCONNECTED'
}"
>
{{ metric.status }}
</div>
</div>
</div>
} @else {
<div class="p-6 text-center text-xs text-slate-400 bg-slate-50 border rounded-xl">
Aguardando emissão do primeiro frame de telemetria RxJS...
</div>
}
<!-- Painel de Logs Escutados Manualmente via takeUntilDestroyed -->
<div class="space-y-1">
<span class="text-[10px] font-bold uppercase text-slate-400">
Logs de Auditoria RxJS (Inscrição protegida por takeUntilDestroyed):
</span>
<div class="p-3 bg-slate-900 text-emerald-400 font-mono text-xs rounded-xl h-24 overflow-y-auto space-y-1">
@for (log of auditEvents; track $index) {
<div>{{ log }}</div>
}
</div>
</div>
</div>
`
})
export class NetworkStatusMonitorComponent {
// 1. BehaviorSubject: Mantém o último valor emitido e o entrega imediatamente a novos inscritos
private metricSubject = new BehaviorSubject<NetworkMetric>({
latencyMs: 18,
packetLossRate: 0.001,
status: 'OPTIMAL'
});
// 2. Observable Público exposto para consumo seguro (somente leitura)
readonly metric$: Observable<NetworkMetric> = this.metricSubject.asObservable();
auditEvents: string[] = [];
constructor() {
// 3. Inscrição Imperativa Segura com takeUntilDestroyed()
// Cancela a assinatura automaticamente quando o componente for destruído pelo Angular
this.metric$
.pipe(takeUntilDestroyed())
.subscribe((data) => {
const msg = `[${new Date().toLocaleTimeString()}] Status alterado para: ${data.status} (Latência: ${data.latencyMs}ms)`;
this.auditEvents.unshift(msg);
});
}
simulateDegradation(): void {
this.metricSubject.next({
latencyMs: 145,
packetLossRate: 0.048,
status: 'DEGRADED'
});
}
resetOptimal(): void {
this.metricSubject.next({
latencyMs: 16,
packetLossRate: 0.0,
status: 'OPTIMAL'
});
}
}
🖥️ Saída / Comportamento Esperado
Renderização Inicial:
AsyncPipe se inscreve no metric$:
Latência: 18 ms | Perda: 0.1% | Qualidade: [OPTIMAL] verde
Audit Log: "[14:52:00] Status alterado para: OPTIMAL (Latência: 18ms)"
Ao clicar em "Simular Degradação":
metricSubject.next(...) emite um novo objeto no stream.
O AsyncPipe detecta a emissão e atualiza o DOM instantaneamente:
Latência: 145 ms | Perda: 4.8% | Qualidade: [DEGRADED] âmbar
Novo evento adicionado à lista de logs auditEvents.
Ao navegar para outra rota (destruição do componente):
takeUntilDestroyed() encerra a subscrição automaticamente sem necessidade de unsubscribe() manual.
O AsyncPipe se desinscreve nativamente, garantindo descarte completo da memória.
🔍 Destaques da Implementação
takeUntilDestroyed()(Angular 16+): Utiliza o contexto de injeção atual para se vincular aoDestroyRefdo componente, tornando o padrãoSubject + takeUntilobsoleto.AsyncPipeGerenciador: Automatiza a subscrição, a desinscrição e a marcação de detecção de mudanças (markForCheck) sem código imperativo no.ts.- Encapsulamento com
asObservable(): Impede que consumidores externos chamem.next()indevidamente na fonte de dados.