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


💻 Código de Exemplo 19 — Code-Splitting Granular com Deferrable Views (@defer)

Este exemplo apresenta a utilização dos blocos declarativos de Deferrable Views (@defer) do Angular 17/18/19, demonstrando como adiar o download e a renderização de componentes pesados através de gatilhos como on viewport, on interaction, prefetch on idle, com blocos de @placeholder, @loading e @error.

1. Componente Pesado a ser Adiado: src/app/components/heavy-analytics-chart.component.ts

// src/app/components/heavy-analytics-chart.component.ts
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-heavy-analytics-chart',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="p-6 bg-slate-900 text-white rounded-2xl border border-slate-800 space-y-4 shadow-xl">
      <div class="flex justify-between items-center border-b border-slate-800 pb-3">
        <h3 class="font-bold text-sm text-emerald-400">📊 Gráfico Analítico de Throughput (ECharts Mock)</h3>
        <span class="text-[10px] bg-slate-800 px-2 py-0.5 rounded text-slate-300 font-mono">
          Chunk: heavy-analytics-chart.js (Baixado sob demanda)
        </span>
      </div>

      <!-- Simulação de Grade Gráfica SVG -->
      <div class="h-32 flex items-end gap-2 pt-4 px-2">
        @for (height of barHeights(); track $index) {
          <div
            class="flex-1 bg-gradient-to-t from-red-600 to-indigo-500 rounded-t transition-all duration-500 hover:opacity-80"
            [style.height.%]="height"
          ></div>
        }
      </div>

      <div class="flex justify-between text-[11px] text-slate-400 font-mono pt-2 border-t border-slate-800">
        <span>Throughput Médio: 48.200 req/s</span>
        <span>Taxa de Erro: 0.02%</span>
      </div>
    </div>
  `
})
export class HeavyAnalyticsChartComponent {
  barHeights = signal<number[]>([35, 60, 45, 80, 95, 70, 85, 40, 65, 90]);
}

2. Componente com Bloco @defer: src/app/components/performance-dashboard.component.ts

// src/app/components/performance-dashboard.component.ts
import { Component, signal } from '@angular/core';
import { HeavyAnalyticsChartComponent } from './heavy-analytics-chart.component';

@Component({
  selector: 'app-performance-dashboard',
  standalone: true,
  imports: [HeavyAnalyticsChartComponent],
  template: `
    <div class="max-w-2xl 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">⚡ Otimização com Deferrable Views (&#64;defer)</h2>
        <p class="text-xs text-slate-500">Divisão de bundle nativa sem necessidade de rotas separadas</p>
      </div>

      <p class="text-xs text-slate-600 leading-relaxed">
        O bloco abaixo contém um componente pesado. O compilador do Angular dividiu este componente em um arquivo JavaScript
        independente. O chunk só será baixado quando o usuário clicar no gatilho ou rolar a página até ele.
      </p>

      <!-- BOTÃO GATILHO PARA CARREGAMENTO MANUAL -->
      <button
        #triggerBtn
        type="button"
        class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-xl text-xs font-bold transition-colors shadow-sm"
      >
        Carregar Módulo Gráfico (Gatilho on interaction)
      </button>

      <!-- BLOCO @DEFER MODERNO COM 4 ESTADOS -->
      @defer (on interaction(triggerBtn); prefetch on idle) {
        <!-- Componente pesado instanciado sob demanda -->
        <app-heavy-analytics-chart />
      } @placeholder (minimum 300ms) {
        <!-- Renderizado inicialmente no HTML antes de qualquer interação -->
        <div class="h-44 border-2 border-dashed border-slate-200 rounded-2xl flex flex-col items-center justify-center p-6 text-center space-y-1">
          <span class="text-2xl">💤</span>
          <p class="text-xs font-semibold text-slate-700">Módulo Gráfico Adiado (Placeholder)</p>
          <p class="text-[11px] text-slate-400">Clique no botão acima para carregar o chunk.</p>
        </div>
      } @loading (after 100ms; minimum 500ms) {
        <!-- Exibido enquanto o arquivo JavaScript está baixando pela rede -->
        <div class="h-44 bg-slate-50 border rounded-2xl flex items-center justify-center gap-2 text-xs text-slate-500 animate-pulse">
          <span class="w-4 h-4 border-2 border-red-600 border-t-transparent rounded-full animate-spin"></span>
          <span>Baixando pacote JavaScript sob demanda...</span>
        </div>
      } @error {
        <!-- Exibido se o download do chunk falhar (ex: rede offline) -->
        <div class="p-4 bg-red-50 border border-red-200 text-red-700 text-xs rounded-xl">
          ⚠️ Falha de rede ao baixar o módulo gráfico. Verifique sua conexão.
        </div>
      }
    </div>
  `
})
export class PerformanceDashboardComponent {}

🖥️ Saída / Comportamento Esperado

Inspeção de Rede (DevTools Network Tab) na Carga da Página:
O arquivo 'heavy-analytics-chart.component.js' NÃO é baixado no carregamento inicial da página!
A tela exibe o bloco @placeholder:
"💤 Módulo Gráfico Adiado (Placeholder)"

Em momento ocioso do navegador (prefetch on idle):
O browser realiza o download prévio do chunk em background sem travar a thread principal.

Ao Clicar no Botão "Carregar Módulo Gráfico":
O gatilho on interaction(triggerBtn) é satisfeito.
O bloco @loading exibe brevemente o spinner animado (mínimo de 500ms para evitar piscar).
O componente <app-heavy-analytics-chart> é montado exibindo o gráfico escuro com barras gradientes.

🔍 Destaques da Implementação

  1. Zero Configuração de Build: O compilador do Angular 18 isola automaticamente os imports dentro de @defer em novos chunks sem alterar o angular.json.
  2. prefetch on idle Estratégico: Faz o pré-carregamento dos bytes em momentos de ociosidade do navegador, garantindo renderização instantânea no clique.
  3. Parâmetros after e minimum: Evitam o efeito desagradável de tela piscando (layout shift e flickering) em conexões de altíssima velocidade.