💻 Exemplos: Capítulo 06: Reatividade Moderna com Angular Signals (signal, computed, effect)
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 06 — Reatividade com signal(), computed(), effect() e untracked()
Este exemplo apresenta a utilização dos três pilares da reatividade de Signals no Angular 18/19: signal (estado primitivo ou estruturado mutável), computed (derivação memoizada livre de efeitos colaterais) e effect (efeitos colaterais assíncronos com limpeza via onCleanup).
// src/app/components/signal-analytics-engine.component.ts
import { Component, signal, computed, effect, untracked } from '@angular/core';
interface TelemetrySample {
cpuUsage: number;
memoryUsage: number;
temperature: number;
}
@Component({
selector: 'app-signal-analytics-engine',
standalone: true,
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="border-b pb-4">
<h2 class="text-xl font-bold text-slate-800">⚡ Motor Analítico com Angular Signals</h2>
<p class="text-xs text-slate-500">Reatividade fina sem Zone.js (Zoneless Foundation)</p>
</div>
<!-- Métricas Primárias em Sinais -->
<div class="grid grid-cols-3 gap-3">
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">CPU Usage</span>
<div class="text-xl font-black text-slate-900 mt-1">{{ telemetry().cpuUsage }}%</div>
</div>
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">Memória RAM</span>
<div class="text-xl font-black text-slate-900 mt-1">{{ telemetry().memoryUsage }}%</div>
</div>
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[10px] uppercase font-bold text-slate-400">Temperatura</span>
<div
class="text-xl font-black mt-1"
[class.text-red-600]="telemetry().temperature > 75"
[class.text-emerald-600]="telemetry().temperature <= 75"
>
{{ telemetry().temperature }}°C
</div>
</div>
</div>
<!-- Derivações Computadas com Computed Signals -->
<div class="p-4 bg-slate-50 border rounded-xl space-y-2">
<div class="flex justify-between items-center text-xs">
<span class="text-slate-600 font-medium">Índice Geral de Estresse (0-100):</span>
<strong class="text-slate-900 font-mono text-sm">{{ stressIndex() }} pts</strong>
</div>
<div class="flex justify-between items-center text-xs">
<span class="text-slate-600 font-medium">Classificação Operacional:</span>
<span
class="px-2.5 py-0.5 rounded-full font-bold text-[10px]"
[class.bg-emerald-100]="healthGrade() === 'ÓTIMO'"
[class.text-emerald-800]="healthGrade() === 'ÓTIMO'"
[class.bg-amber-100]="healthGrade() === 'ALERTA'"
[class.text-amber-800]="healthGrade() === 'ALERTA'"
[class.bg-red-100]="healthGrade() === 'CRÍTICO'"
[class.text-red-800]="healthGrade() === 'CRÍTICO'"
>
{{ healthGrade() }}
</span>
</div>
</div>
<!-- Controles de Ação com .set() e .update() -->
<div class="flex gap-2">
<button
type="button"
(click)="injectLoadSpike()"
class="flex-1 py-2 bg-red-600 hover:bg-red-700 text-white rounded-xl text-xs font-bold transition-colors"
>
Simular Pico de Carga (.update)
</button>
<button
type="button"
(click)="resetCooling()"
class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl text-xs font-semibold"
>
Resfriar (.set)
</button>
</div>
<!-- Log de Efeitos Gerados pelo effect() -->
<div class="space-y-1">
<span class="text-[10px] font-bold uppercase text-slate-400 tracking-wider">
Auditoria de Efeitos Reativos (effect):
</span>
<div class="p-3 bg-slate-900 text-emerald-400 font-mono text-xs rounded-xl h-28 overflow-y-auto space-y-1">
@for (log of auditLogs(); track $index) {
<div>{{ log }}</div>
}
</div>
</div>
</div>
`
})
export class SignalAnalyticsEngineComponent {
// 1. Sinal Gravável Principal (Writable Signal)
telemetry = signal<TelemetrySample>({
cpuUsage: 35,
memoryUsage: 48,
temperature: 52
});
auditLogs = signal<string[]>([]);
private sessionTag = 'PROD-ZONE-1';
// 2. Computed Signal (Cálculo Puro Memoizado)
stressIndex = computed<number>(() => {
const { cpuUsage, memoryUsage, temperature } = this.telemetry();
// Fórmula ponderada de estresse
const raw = (cpuUsage * 0.4) + (memoryUsage * 0.3) + ((temperature / 100) * 30);
return Math.round(raw);
});
healthGrade = computed<'ÓTIMO' | 'ALERTA' | 'CRÍTICO'>(() => {
const index = this.stressIndex();
if (index >= 75) return 'CRÍTICO';
if (index >= 50) return 'ALERTA';
return 'ÓTIMO';
});
constructor() {
// 3. effect(): Executado automaticamente sempre que dependências mudarem
effect((onCleanup) => {
const currentGrade = this.healthGrade();
const currentStress = this.stressIndex();
// untracked: Lê um dado sem cadastrá-lo como gatilho do efeito
const tag = untracked(() => this.sessionTag);
const logMsg = `[${new Date().toLocaleTimeString()}] [${tag}] Status: ${currentGrade} (Índice: ${currentStress})`;
this.auditLogs.update((curr) => [logMsg, ...curr.slice(0, 7)]);
onCleanup(() => {
// Executado imediatamente antes da próxima execução do efeito ou destruição
});
});
}
// 4. Mutações com .update()
injectLoadSpike(): void {
this.telemetry.update((prev) => ({
cpuUsage: Math.min(100, prev.cpuUsage + 25),
memoryUsage: Math.min(100, prev.memoryUsage + 15),
temperature: Math.min(95, prev.temperature + 12)
}));
}
// 5. Mutações com .set()
resetCooling(): void {
this.telemetry.set({
cpuUsage: 20,
memoryUsage: 35,
temperature: 42
});
}
}
🖥️ Saída / Comportamento Esperado
Renderização Inicial:
CPU: 35% | RAM: 48% | Temp: 52°C
Índice de Estresse: 44 pts | Classificação: [ÓTIMO] verde
Log Inicial no Terminal Escuro:
"[14:35:10] [PROD-ZONE-1] Status: ÓTIMO (Índice: 44)"
Interação 1 — Clicar em "Simular Pico de Carga" (1ª vez):
CPU salta para 60%, RAM para 63%, Temp para 64°C.
Índice de Estresse computado: 62 pts.
Classificação muda para [ALERTA] âmbar.
Novo log inserido no topo via effect().
Interação 2 — Clicar em "Simular Pico de Carga" (2ª vez):
CPU salta para 85%, RAM para 78%, Temp para 76°C (destacado em vermelho).
Índice de Estresse: 80 pts. Classificação: [CRÍTICO] vermelho.
Novo log de auditoria gerado instantaneamente.
Interação 3 — Clicar em "Resfriar (.set)":
Valores retornam a 20%, 35%, 42°C.
Índice recua para 27 pts e classificação volta para ÓTIMO.
🔍 Destaques da Implementação
- Grafo de Dependências sem Zone.js: O Angular sabe cirurgicamente quais nós do DOM dependem de
healthGrade(), sem disparar detecção de mudanças sujas em toda a árvore. - Memoização Automática: Se
telemetryfor atualizado mas o resultado do cálculo emstressIndexfor idêntico ao anterior, os consumidores dohealthGradenão são reavaliados. untracked()Estratégico: Impede que alterações em variáveis secundárias disparem o callback doeffect.