Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 02 — Data Binding Unidirecional e Bidirecional com Standalone Components

Este exemplo apresenta os 4 pilares de Data Binding do Angular: Interpolação {{ }}, Property Binding [prop], Event Binding (event) com modificadores de teclado e Two-Way Data Binding [(ngModel)] através do FormsModule.

// src/app/components/server-config-editor.component.ts
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

interface DatabaseServerConfig {
  hostname: string;
  port: number;
  maxConnections: number;
  sslEnabled: boolean;
  environment: 'staging' | 'production' | 'disaster-recovery';
}

@Component({
  selector: 'app-server-config-editor',
  standalone: true,
  imports: [CommonModule, FormsModule],
  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-3">
        <h2 class="text-xl font-bold text-slate-800">
          ⚙️ Editor de Configuração do Banco de Dados
        </h2>
        <p class="text-xs text-slate-500">
          Demonstração de Data Binding no Angular 18 Standalone
        </p>
      </div>

      <!-- Formulário de Edição Interativa -->
      <div class="space-y-4">
        <!-- Two-Way Binding com [(ngModel)] -->
        <div>
          <label class="block text-xs font-semibold text-slate-700 mb-1">
            Hostname do Servidor:
          </label>
          <input
            type="text"
            [(ngModel)]="config.hostname"
            class="w-full border px-3 py-2 rounded-lg text-sm outline-none focus:ring-2 focus:ring-red-500"
          />
        </div>

        <div class="grid grid-cols-2 gap-3">
          <div>
            <label class="block text-xs font-semibold text-slate-700 mb-1">Porta TCP:</label>
            <input
              type="number"
              [(ngModel)]="config.port"
              class="w-full border px-3 py-2 rounded-lg text-sm"
            />
          </div>

          <div>
            <label class="block text-xs font-semibold text-slate-700 mb-1">Ambiente:</label>
            <select
              [(ngModel)]="config.environment"
              class="w-full border px-3 py-2 rounded-lg text-sm bg-white"
            >
              <option value="staging">Staging (Homologação)</option>
              <option value="production">Production (Produção)</option>
              <option value="disaster-recovery">Disaster Recovery (DR)</option>
            </select>
          </div>
        </div>

        <!-- Property Binding de Estilo e Atributo -->
        <div>
          <div class="flex justify-between text-xs font-semibold text-slate-700 mb-1">
            <span>Conexões Máximas Permitidas:</span>
            <span class="font-mono text-red-600 font-bold">{{ config.maxConnections }}</span>
          </div>
          <input
            type="range"
            min="50"
            max="1000"
            step="50"
            [(ngModel)]="config.maxConnections"
            class="w-full h-2 bg-slate-200 rounded-lg cursor-pointer"
          />
          <!-- Barra de Progresso com Property Binding de Estilo dinâmico [style.width.%] -->
          <div class="w-full bg-slate-100 rounded-full h-2 mt-2 overflow-hidden border">
            <div
              class="h-2 rounded-full transition-all duration-300"
              [style.width.%]="(config.maxConnections / 1000) * 100"
              [ngClass]="{
                'bg-emerald-500': config.maxConnections <= 400,
                'bg-amber-500': config.maxConnections > 400 && config.maxConnections <= 750,
                'bg-red-600': config.maxConnections > 750
              }"
            ></div>
          </div>
        </div>

        <label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer pt-1">
          <input type="checkbox" [(ngModel)]="config.sslEnabled" class="rounded text-red-600" />
          <span>Exigir Conexão Criptografada SSL/TLS Obrigatória</span>
        </label>
      </div>

      <!-- Event Binding com (click) e Property Binding [disabled] -->
      <div class="flex gap-3 pt-2 border-t">
        <button
          type="button"
          (click)="saveConfiguration()"
          [disabled]="isSaving()"
          class="flex-1 py-2.5 bg-red-600 hover:bg-red-700 text-white rounded-xl text-xs font-bold transition-colors disabled:opacity-50"
        >
          {{ isSaving() ? 'Persistindo no Cluster...' : 'Aplicar Configuração' }}
        </button>
        <button
          type="button"
          (click)="resetDefaults()"
          class="px-4 py-2.5 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl text-xs font-semibold"
        >
          Resetar
        </button>
      </div>

      <!-- Interpolação e Diagnóstico JSON em Tempo Real -->
      <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 em Tempo Real (Interpolação):</div>
        <div>URI: {{ buildConnectionUri() }}</div>
        <div>Segurança: {{ config.sslEnabled ? 'SSL_VERIFIED' : 'PLAINTEXT_INSECURE' }}</div>
      </div>
    </div>
  `
})
export class ServerConfigEditorComponent {
  config: DatabaseServerConfig = {
    hostname: 'primary-db.prod.internal',
    port: 5432,
    maxConnections: 500,
    sslEnabled: true,
    environment: 'production'
  };

  isSaving = signal<boolean>(false);

  buildConnectionUri(): string {
    const protocol = this.config.sslEnabled ? 'postgresql+ssl://' : 'postgresql://';
    return `${protocol}${this.config.hostname}:${this.config.port}/analytics_db`;
  }

  saveConfiguration(): void {
    this.isSaving.set(true);
    setTimeout(() => {
      this.isSaving.set(false);
      console.log('[Config Saved] Servidor atualizado:', this.config);
    }, 800);
  }

  resetDefaults(): void {
    this.config = {
      hostname: 'localhost',
      port: 5432,
      maxConnections: 100,
      sslEnabled: false,
      environment: 'staging'
    };
  }
}

🖥️ Saída / Comportamento Esperado

Renderização Inicial:
Hostname: "primary-db.prod.internal" | Porta: 5432 | Ambiente: Production
Conexões: 500 / 1000 (Barra 50% preenchida na cor âmbar)
URI Gerada: "postgresql+ssl://primary-db.prod.internal:5432/analytics_db"

Interação 1 — Alterar Hostname:
Digitar "replica-read-01.corp" no campo:
A interpolação no bloco escuro atualiza instantaneamente para "postgresql+ssl://replica-read-01.corp:5432/analytics_db".

Interação 2 — Deslizar conexões para 850:
A barra de progresso avança para 85% e sua classe muda dinamicamente para 'bg-red-600' através do [ngClass].

Interação 3 — Clicar em "Aplicar Configuração":
O botão exibe "Persistindo no Cluster..." e fica desabilitado via [disabled]="isSaving()".
Após 800ms, o botão volta ao normal e o console registra o objeto atualizado.

🔍 Destaques da Implementação

  1. Binding de Propriedade de Estilo Direto: [style.width.%]="..." elimina concatenações de strings "width: " + valor + "%".
  2. Two-Way Binding com [(ngModel)]: O operador “banana in a box” sincroniza automaticamente a propriedade da classe com o evento input do HTML.
  3. Imunidade a Injeção: A interpolação {{ }} realiza sanitização automática contra vulnerabilidades de Cross-Site Scripting (XSS).