Capítulo 03: Control Flow Syntax Moderna (@if, @for, @switch) e Diretivas
Capítulo 03: Control Flow Syntax Moderna (@if, @for, @switch) e Diretivas
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual do Novo Controle de Fluxo Embutido
flowchart TD
A["Template HTML com Nova Sintaxe (@)"] --> B["Compilador Angular 18 (@angular/compiler)"]
subgraph BLOCOS_EMBUTIDOS["Sintaxe de Controle de Fluxo Nativa"]
C["@if (condicao) { ... } @else { ... }"]
D["@for (item of items; track item.id) { ... } @empty { ... }"]
E["@switch (status) { @case ('A') { ... } @default { ... } }"]
end
B --> BLOCOS_EMBUTIDOS
C --> F["Zero overhead de diretivas micro-sintáticas (*ngIf)"]
D --> G["Algoritmo de Reconciliação com track obrigatório"]
E --> H["Avaliação direta sem containers auxiliares (*ngSwitch)"]
F & G & H --> I["Compilação em Bytecode de Template Ultra-Rápido"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style BLOCOS_EMBUTIDOS fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style I fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. A Nova Sintaxe de Controle de Fluxo Nativa
No Angular 17 e consolidado no Angular 18, as diretivas estruturais legadas (*ngIf, *ngFor e *ngSwitch) foram descontinuadas em favor de uma Sintaxe de Controle de Fluxo Embutida no Compilador (Built-in Control Flow), identificada pelo prefixo @.
1.1 Por que o Angular Abandonou as Diretivas Estruturais?
As diretivas com asterisco (*ngIf) funcionavam como syntactic sugar que expandia o HTML em nós <ng-template> nos bastidores. Esse mecanismo gerava três problemas graves:
- Obrigatoriedade de Importar
CommonModule: Nenhum componente podia fazer um simplesifsem importar módulos inteiros. - Performance Inferior: O compilador precisava instanciar diretivas e contextos de visualização (ViewContainerRef) para cada repetição.
- Erros Frequentes de Reconciliação: No
*ngFor, a funçãotrackByera opcional. Quando os desenvolvedores esqueciam de utilizá-la, o Angular recriava 100% dos nós do DOM a cada pequena alteração de array.
1.2 O Bloco @if e Encadeamentos @else if / @else
O @if oferece uma estrutura declarativa e elegante, com inferência de tipos estreitada (type narrowing) que elimina checagens de null:
@if (user(); as u) {
<p>Bem-vindo, {{ u.name }}!</p>
} @else if (isLoading()) {
<p>Carregando perfil...</p>
} @else {
<p>Usuário deslogado.</p>
}
1.3 O Bloco @for: track Obrigatório e Bloco @empty
No @for, o compilador do Angular obriga o desenvolvedor a declarar uma expressão de rastreamento (track item.id):
- Otimização Extrema de DOM: O Angular utiliza a chave declarada em
trackpara reutilizar elementos físicos no navegador, resultando em ganhos de performance de até 90% em listas grandes. - Variáveis Contextuais Embutidas:
$index(índice 0-based),$count(tamanho total),$first(booleano se é o primeiro),$last(booleano se é o último),$even(booleano se é par),$odd(booleano se é ímpar). - Bloco
@emptyIntegrado: Renderizado automaticamente caso o array seja nulo ou possua comprimento zero, eliminando blocos*ngIfsecundários de empty-state.
1.4 O Bloco @switch
Substitui a combinação prolixa de [ngSwitch], *ngSwitchCase e *ngSwitchDefault por uma sintaxe limpa inspirada no TypeScript moderno:
@switch (pedido.status) {
@case ('PAGO') { <span class="badge-verde">Pago</span> }
@case ('PENDENTE') { <span class="badge-amarela">Aguardando</span> }
@default { <span class="badge-cinza">Indefinido</span> }
}
⚖️ 2. Tabela Comparativa: Diretivas Estruturais Legadas vs Modern Control Flow
| Recurso Técnico | Diretivas Legadas (*ngIf, *ngFor) |
Modern Built-in Control Flow (@if, @for) |
|---|---|---|
| Dependência de Módulo | Exige CommonModule no array imports. |
Zero dependências (embutido no compilador). |
| Rastreamento de Itens | trackBy: fn (Opcional, frequentemente esquecido). |
track expr Obrigatório em tempo de compilação. |
| Tratamento de Lista Vazia | Exigia <div *ngIf="items.length === 0"> manual. |
Bloco nativo @empty integrado. |
Suporte a else if |
Não suportado (exigia múltiplos *ngIf complexos). |
Suporte nativo @else if. |
| Velocidade de Reconciliação | Moderada. | Até 90% mais rápida no cálculo de diffing. |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Usar track $index em Listas com Mutações Intermediárias
Erro Comum: Declarar @for (item of items; track $index). Se a lista sofrer inserção no topo, ordenação ou deleção, o índice muda de referência e o Angular reaproveitará nós com dados incorretos.
Abordagem Correta: Use sempre uma chave identificadora única e estável do modelo de dados: track item.id ou track item.uuid.
Anti-Pattern 2: Esquecer Parênteses ao Ler Sinais dentro de Blocos @if
Erro Comum: Escrever @if (isLoading) quando isLoading é um Signal (signal(false)). Como o Signal é uma função, a referência da função é truthy, avaliando a condição incorretamente como verdadeira.
Abordagem Correta: Invoque o Signal: @if (isLoading()).
💻 4. Código Conceitual Arquitetural Comentado
// src/app/components/cluster-nodes-table.component.ts
import { Component, signal } from '@angular/core';
interface NodeSummary {
id: string;
name: string;
role: 'MASTER' | 'WORKER' | 'INGRESS';
isReady: boolean;
}
@Component({
selector: 'app-cluster-nodes-table',
standalone: true, // Não precisa de CommonModule para @if e @for!
template: `
<div class="max-w-xl mx-auto p-5 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-4">
<div class="flex justify-between items-center border-b pb-3">
<h3 class="font-bold text-slate-800 text-sm">Nós do Cluster (Novo Control Flow)</h3>
<button
type="button"
(click)="nodes.set([])"
class="text-xs text-red-500 hover:text-red-700 underline"
>
Simular Lista Vazia (@empty)
</button>
</div>
<!-- NOVO CONTROL FLOW @for COM track ESTÁVEL E BLOCO @empty -->
<div class="border rounded-xl divide-y overflow-hidden text-xs">
@for (node of nodes(); track node.id; let idx = $index; let count = $count) {
<div class="p-3 flex justify-between items-center hover:bg-slate-50">
<div>
<span class="text-slate-400 font-mono mr-2">#{{ idx + 1 }}/{{ count }}</span>
<strong class="text-slate-800">{{ node.name }}</strong>
</div>
<div class="flex items-center gap-2">
<!-- NOVO CONTROL FLOW @switch -->
@switch (node.role) {
@case ('MASTER') {
<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-purple-100 text-purple-800">MASTER</span>
}
@case ('INGRESS') {
<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-blue-100 text-blue-800">INGRESS</span>
}
@default {
<span class="px-2 py-0.5 rounded text-[10px] font-bold bg-slate-100 text-slate-800">WORKER</span>
}
}
<!-- NOVO CONTROL FLOW @if / @else -->
@if (node.isReady) {
<span class="text-emerald-600 font-bold">✓ Ready</span>
} @else {
<span class="text-red-600 font-bold">✕ NotReady</span>
}
</div>
</div>
} @empty {
<!-- Bloco renderizado automaticamente quando a lista estiver vazia -->
<div class="p-6 text-center text-slate-400">
Nenhum nó registrado no cluster.
</div>
}
</div>
</div>
`
})
export class ClusterNodesTableComponent {
nodes = signal<NodeSummary[]>([
{ id: 'node-01', name: 'master-control-plane-01', role: 'MASTER', isReady: true },
{ id: 'node-02', name: 'worker-pool-compute-02', role: 'WORKER', isReady: true },
{ id: 'node-03', name: 'ingress-edge-router-03', role: 'INGRESS', isReady: false }
]);
}
🛠️ 5. Cheat-Sheet da Sintaxe de Controle de Fluxo
# Comando de migração automática do Angular CLI (converte todo o projeto para @if e @for)
ng g @angular/core:control-flow
<!-- Bloco @if / @else if / @else -->
@if (isAdmin()) {
<AdminDashboard />
} @else if (isOperator()) {
<OperatorPanel />
} @else {
<LoginPrompt />
}
<!-- Bloco @for com variáveis de contexto -->
@for (item of items(); track item.id; let i = $index; let isLast = $last) {
<div>{{ i }} - {{ item.nome }} {{ isLast ? '(Último)' : '' }}</div>
} @empty {
<p>Nenhum item encontrado.</p>
}