Capítulo 02: Templates, Property Binding, Event Binding e Two-Way Binding

Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR


🗺️ Mapa Conceitual do Sistema de Data Binding do Angular

flowchart TD
    A["Estado na Classe TypeScript (.ts)"] -->|"1. Interpolação: {{ expr }}"| B["DOM / HTML Renderizado"]
    A -->|"2. Property Binding: [prop]='valor'"| B
    B -->|"3. Event Binding: (evento)='metodo($event)'"| A
    A <-->|"4. Two-Way Binding: [(ngModel)]='propriedade'"| B

    subgraph SEGURANCA["Segurança e Sanitização Nativa"]
        C["DomSanitizer: Prevenção Automática de XSS"]
        D["Context-Aware Escaping de URLs e Scripts"]
        E["Diferenciação Estrita: [prop] vs [attr.nome]"]
    end

    B --- SEGURANCA

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style SEGURANCA fill:#ede7f6,stroke:#7e57c2,stroke-width:2px

🏛️ 1. O Compilador de Templates e os Modos de Vinculação de Dados

No Angular, os templates HTML não são meras strings avaliadas em tempo de execução; eles são compilados estaticamente durante o build em instruções otimizadas de criação e atualização de nós do DOM. O sistema de vinculação de dados (Data Binding) conecta o estado da classe do componente à árvore de visualização.

1.1 Os 4 Pilares de Data Binding

  1. Interpolação ({{ expressao }}): Avalia expressões síncronas e insere texto puro no nó DOM. Se a expressão retornar null ou undefined, o Angular insere uma string vazia silenciosamente sem quebrar o layout.
  2. Property Binding ([propriedade]="valor"): Conecta uma propriedade do elemento DOM (ex: disabled, src, hidden, value) ou de uma @Input / input() de componente filho a um dado do componente pai.
  3. Event Binding ((evento)="handler($event)"): Escuta eventos nativos do navegador (click, keyup, submit) ou saídas customizadas (output()), invocando métodos da classe.
  4. Two-Way Data Binding ([(ngModel)]="valor"): Conhecido como sintaxe “banana in a box”, combina Property Binding com Event Binding para formulários bidirecionais. Exige a importação explícita de FormsModule.

1.2 Property Binding vs Attribute Binding ([attr.nome])

Uma das maiores fontes de confusão no Angular é a diferença entre propriedades do objeto DOM e atributos HTML:

  • Propriedade DOM: Representa o estado vivo em memória do elemento (ex: HTMLInputElement.disabled). Controla-se com [disabled]="condicao".
  • Atributo HTML: Representa o texto original declarado no HTML estático (ex: colspan, aria-label, data-testid). Não são propriedades nativas do objeto JavaScript do elemento, exigindo o prefixo attr.:
    <button [attr.aria-label]="isExpanded ? 'Recolher menu' : 'Expandir menu'">
    

1.3 Segurança Contra Ataques XSS e DomSanitizer

O Angular trata todo valor inserido via interpolação ou property binding como não confiável por padrão. Ele aplica automaticamente o Context-Aware Escaping:

  • Se uma string contiver <script>alert('hack')</script>, o Angular sanitiza os caracteres transformando-os em entidades HTML inofensivas.
  • Para injetar HTML de forma deliberada (ex: rich text de um CMS), deve-se utilizar a propriedade [innerHTML] em conjunto com o serviço DomSanitizer (bypassSecurityTrustHtml).

⚖️ 2. Tabela Comparativa de Sintaxes de Data Binding

Tipo de Binding Sintaxe Canônica Direção do Fluxo Exemplo de Aplicação
Interpolação {{ expressao }} Classe ➔ DOM <h3>{{ user.name }}</h3>
Property Binding [prop]="valor" Classe ➔ DOM <button [disabled]="isPending">
Attribute Binding [attr.nome]="valor" Classe ➔ DOM <td [attr.colspan]="colCount">
Class Binding [class.nome]="bool" Classe ➔ DOM <div [class.is-active]="active">
Style Binding [style.prop.unidade]="num" Classe ➔ DOM <div [style.width.%]="progresso">
Event Binding (evento)="acao($event)" DOM ➔ Classe <input (keydown.enter)="salvar()">
Two-Way Binding [(ngModel)]="prop" Classe 🔁 DOM <input [(ngModel)]="username">

⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)

Anti-Pattern 1: Executar Funções Pesadas dentro de Interpolações {{ }}

Erro Comum: Escrever <span>{{ calcularRelatorioComplexo(dados) }}</span> no template. Consequência: Em aplicações com Zone.js, essa função será executada dezenas de vezes a cada clique, scroll ou movimento de mouse, destruindo a taxa de quadros (FPS) da aplicação. Abordagem Correta: Pré-calcule o valor em uma propriedade, utilize um computed() signal ou crie um Pure Pipe (pure: true).

Anti-Pattern 2: Esquecer de Importar FormsModule ao Usar [(ngModel)]

Erro Comum: Utilizar [(ngModel)] em um componente standalone sem declarar imports: [FormsModule]. O compilador gerará o erro: Can't bind to 'ngModel' since it isn't a known property of 'input'.


💻 4. Código Conceitual Arquitetural Comentado

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

@Component({
  selector: 'app-server-port-editor',
  standalone: true,
  imports: [FormsModule],
  template: `
    <div class="max-w-md mx-auto p-5 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-4">
      <h3 class="font-bold text-slate-800 text-sm">Configuração de Porta de Rede</h3>

      <!-- Two-Way Binding com [(ngModel)] -->
      <div>
        <label class="block text-xs font-semibold text-slate-700 mb-1">Porta do Serviço:</label>
        <input
          type="number"
          [(ngModel)]="portNumber"
          class="w-full border px-3 py-2 rounded-lg text-sm outline-none focus:ring-2 focus:ring-red-500"
          [class.border-red-400]="isPrivilegedPort()"
        />
      </div>

      <!-- Class Binding e Property Binding de Estilo dinâmico -->
      <div
        class="p-3 rounded-xl border text-xs flex justify-between items-center transition-colors"
        [class]="isPrivilegedPort() ? 'bg-amber-50 border-amber-200 text-amber-800' : 'bg-slate-50 border-slate-200 text-slate-700'"
      >
        <span>Classificação:</span>
        <strong class="font-mono">
          {{ isPrivilegedPort() ? 'Porta Privilegiada (Root Obrigatório)' : 'Porta de Usuário Livre' }}
        </strong>
      </div>

      <!-- Event Binding com modificador de tecla enter -->
      <button
        type="button"
        (click)="applyPort()"
        [disabled]="portNumber < 1 || portNumber > 65535"
        class="w-full py-2 bg-red-600 hover:bg-red-700 text-white rounded-xl text-xs font-bold transition-colors disabled:opacity-40"
      >
        Validar e Salvar Porta
      </button>
    </div>
  `
})
export class ServerPortEditorComponent {
  portNumber = 8080;

  isPrivilegedPort(): boolean {
    return this.portNumber < 1024;
  }

  applyPort(): void {
    console.log(`[Config] Porta ${this.portNumber} validada e salva no cluster.`);
  }
}

🛠️ 5. Cheat-Sheet de Data Binding

<!-- Property e Attribute Bindings -->
<img [src]="avatarUrl" [alt]="userName" />
<button [attr.aria-disabled]="isDisabled">Acessibilidade</button>

<!-- Style e Class Bindings -->
<div [class.is-danger]="hasErrors" [class.is-valid]="isValid"></div>
<div [style.max-height.px]="maxHeight" [style.color]="statusColor"></div>

<!-- Event Bindings com Modificadores -->
<input (keydown.enter)="submeter()" (keydown.esc)="cancelar()" />
<div (click)="onClick($event)"></div>