💻 Exemplos: Capítulo 05: Comunicação entre Componentes com input(), output() e Signals
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 05 — Comunicação Tipada com Signal Inputs input(), output() e model()
Este exemplo apresenta o modelo moderno de comunicação entre componentes no Angular 17.1+/18/19, substituindo os antigos decoradores @Input() e @Output() pelas funções funcionais baseadas em Signals: input.required(), output() e model() (Two-Way Binding nativo).
1. Componente Filho: src/app/components/threshold-slider.component.ts
// src/app/components/threshold-slider.component.ts
import { Component, input, output, model, computed } from '@angular/core';
@Component({
selector: 'app-threshold-slider',
standalone: true,
template: `
<div class="border rounded-xl p-4 bg-slate-50 space-y-3">
<div class="flex justify-between items-center">
<label class="text-xs font-bold text-slate-700 uppercase tracking-wider">
{{ label() }}
</label>
<span class="text-xs font-mono font-bold text-red-600 bg-red-50 px-2 py-0.5 rounded">
{{ value() }} {{ unit() }}
</span>
</div>
<!-- Two-Way Binding nativo do signal model() -->
<div class="flex items-center gap-3">
<button
type="button"
(click)="decrement()"
[disabled]="value() <= min()"
class="w-7 h-7 rounded-lg bg-white border shadow-sm font-bold text-slate-700 hover:bg-slate-100 disabled:opacity-40"
>
-
</button>
<input
type="range"
[min]="min()"
[max]="max()"
[step]="step()"
[value]="value()"
(input)="onRangeChange($event)"
class="flex-1 h-2 bg-slate-200 rounded-lg cursor-pointer accent-red-600"
/>
<button
type="button"
(click)="increment()"
[disabled]="value() >= max()"
class="w-7 h-7 rounded-lg bg-white border shadow-sm font-bold text-slate-700 hover:bg-slate-100 disabled:opacity-40"
>
+
</button>
</div>
<div class="flex justify-between items-center text-[11px] text-slate-400 font-mono">
<span>Mín: {{ min() }}</span>
<span>Máx: {{ max() }}</span>
</div>
</div>
`
})
export class ThresholdSliderComponent {
// 1. Signal Inputs (Somente Leitura e Tipadas)
label = input.required<string>(); // input obrigatória exigida pelo compilador
min = input<number>(0);
max = input<number>(100);
step = input<number>(5);
unit = input<string>('%');
// 2. Signal Model: Two-Way Data Binding nativo (leitura e escrita síncronas)
value = model<number>(50);
// 3. Signal Output: Emissão funcional de eventos
thresholdExceeded = output<{ metric: string; currentVal: number }>();
increment(): void {
const next = this.value() + this.step();
if (next <= this.max()) {
this.value.set(next);
this.checkLimits();
}
}
decrement(): void {
const prev = this.value() - this.step();
if (prev >= this.min()) {
this.value.set(prev);
}
}
onRangeChange(event: Event): void {
const target = event.target as HTMLInputElement;
this.value.set(Number(target.value));
this.checkLimits();
}
private checkLimits(): void {
if (this.value() >= this.max() * 0.9) {
this.thresholdExceeded.emit({
metric: this.label(),
currentVal: this.value()
});
}
}
}
2. Componente Pai Consumidor: src/app/components/cluster-quota-manager.component.ts
// src/app/components/cluster-quota-manager.component.ts
import { Component, signal } from '@angular/core';
import { ThresholdSliderComponent } from './threshold-slider.component';
@Component({
selector: 'app-cluster-quota-manager',
standalone: true,
imports: [ThresholdSliderComponent],
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">🎛️ Gestão de Cotas de Recursos</h2>
<p class="text-xs text-slate-500">Comunicação pai-filho via input(), output() e model()</p>
</div>
<!-- Alerta Disparado pelo Signal Output -->
@if (alertBanner()) {
<div class="p-3 bg-amber-50 border border-amber-200 text-amber-900 rounded-xl text-xs font-semibold">
{{ alertBanner() }}
</div>
}
<div class="space-y-4">
<!-- Two-Way Binding limpo [(value)] gerado automaticamente pelo model() -->
<app-threshold-slider
label="Limite de CPU do Cluster"
[min]="10"
[max]="100"
[step]="5"
unit="%"
[(value)]="cpuQuota"
(thresholdExceeded)="onQuotaExceeded($event)"
/>
<app-threshold-slider
label="Cota de Memória RAM"
[min]="4"
[max]="64"
[step]="4"
unit="GB"
[(value)]="ramQuota"
(thresholdExceeded)="onQuotaExceeded($event)"
/>
</div>
<!-- Inspeção do Estado Reativo do Pai -->
<div class="p-4 bg-slate-900 text-slate-200 rounded-xl font-mono text-xs space-y-1">
<div class="text-emerald-400 font-bold">// Estado Reativo do Componente Pai:</div>
<div>cpuQuota Signal: {{ cpuQuota() }}%</div>
<div>ramQuota Signal: {{ ramQuota() }}GB</div>
</div>
</div>
`
})
export class ClusterQuotaManagerComponent {
cpuQuota = signal<number>(75);
ramQuota = signal<number>(32);
alertBanner = signal<string | null>(null);
onQuotaExceeded(event: { metric: string; currentVal: number }): void {
this.alertBanner.set(`⚠️ Alerta Crítico: ${event.metric} ultrapassou 90% da capacidade (${event.currentVal})!`);
}
}
🖥️ Saída / Comportamento Esperado
Renderização Inicial:
Slider 1 (CPU): "LIMITE DE CPU DO CLUSTER" | Valor: 75 %
Slider 2 (RAM): "COTA DE MEMÓRIA RAM" | Valor: 32 GB
Painel JSON do Pai: cpuQuota: 75%, ramQuota: 32GB
Interação 1 — Clicar no botão (+) da CPU até 95%:
O valor sobe de 75 -> 80 -> 85 -> 90 -> 95.
Ao passar de 90%, o método checkLimits() do filho emite o signal output thresholdExceeded.
Banner âmbar surge no pai:
"⚠️ Alerta Crítico: Limite de CPU do Cluster ultrapassou 90% da capacidade (95)!"
O painel escuro do pai reflete: "cpuQuota Signal: 95%".
Interação 2 — Deslizar range da RAM para 16 GB:
O valor atualiza o signal ramQuota no pai sincronizadamente via [(value)].
🔍 Destaques da Implementação
input.required<T>(): O compilador TypeScript emite erro estático na esteira de build caso o desenvolvedor omita a prop obrigatória no template do pai.model<T>(): Unifica entrada e saída em um único objeto de leitura e escrita, dispensando a declaração manual de@Input() valuee@Output() valueChange.output<T>()Funcional: Não depende do antigoEventEmitterdo RxJS, tornando a emissão de eventos mais leve e performática.