Capítulo 04: Pipes Nativos (DatePipe, CurrencyPipe) e Custom Pipes

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


🗺️ Mapa Conceitual de Pipes e Transformação de Dados na View

flowchart TD
    A["Estado Bruto do Componente (Model / Signal)"] --> B["Template Expression (HTML)"]
    B --> C["Operador Pipe (|)"]

    subgraph PIPES_ENGINE["Mecanismo de Transformação de Pipes"]
        D["Pipes Puros (Pure: true, Default)"]
        E["Pipes Impuros (Pure: false)"]
        F["Pipes Nativos (@angular/common)"]
        G["Custom Pipes (@Pipe Standalone)"]
    end

    C --> PIPES_ENGINE
    D --> H["Memoização por Referência de Entrada (Object.is)"]
    E --> I["Execução em Todo Ciclo de Change Detection"]
    F --> J["DatePipe, CurrencyPipe, DecimalPipe, JsonPipe, AsyncPipe"]
    G --> K["Implementação de PipeTransform.transform()"]

    H & J & K --> L["Renderização Formatada no DOM da View"]

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style PIPES_ENGINE fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style L fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. Papel Arquitetural dos Pipes no Angular 18+

No ecossistema Angular, os Pipes são funções puras de formatação declarativa que transformam valores brutos (números, datas, strings, objetos) em formatos amigáveis para exibição visual no template HTML, sem mutar o estado original armazenado nos signals ou no componente.

1.1 Separação Rígida entre Lógica de Negócio e Apresentação

Um dos maiores erros em arquiteturas de front-end é poluir o TypeScript com métodos de formatação invocados dentro de templates ({{ formatarData(item.data) }}). Métodos invocados em templates são reexecutados a cada ciclo de detecção de mudanças (Change Detection), degradando drasticamente o FPS da aplicação.

Os Pipes resolvem esse gargalo através de memoização nativa:

  • O Angular armazena em cache o último valor de entrada e o último resultado calculado.
  • Se a referência da entrada não mudou (Object.is(oldVal, newVal) === true), o pipe simplesmente retorna o valor cacheado sem reexecutar seu código.

⚙️ 2. Pipes Nativos Essenciais do Angular Standalone

Com a arquitetura Standalone, os pipes nativos do pacote @angular/common devem ser importados diretamente no array imports do componente:

import { Component, signal } from '@angular/core';
import { DatePipe, CurrencyPipe, DecimalPipe, PercentPipe, UpperCasePipe, JsonPipe } from '@angular/common';

@Component({
  selector: 'app-relatorio-vendas',
  standalone: true,
  imports: [DatePipe, CurrencyPipe, DecimalPipe, PercentPipe, UpperCasePipe, JsonPipe],
  template: `
    <div class="card">
      <h2>Relatório: {{ empresa() | uppercase }}</h2>
      <p>Data de Emissão: {{ dataEmissao() | date:'dd/MM/yyyy HH:mm' }}</p>
      <p>Total Faturado: {{ faturamento() | currency:'BRL':'symbol':'1.2-2':'pt-BR' }}</p>
      <p>Margem de Lucro: {{ margem() | percent:'1.1-2' }}</p>
      <p>Volume de Transações: {{ transacoes() | number:'1.0-0' }}</p>
    </div>
  `
})
export class RelatorioVendasComponent {
  empresa = signal('Tech Corp Enterprise');
  dataEmissao = signal(new Date());
  faturamento = signal(1254390.75);
  margem = signal(0.245);
  transacoes = signal(8950);
}

2.1 Configuração Global de Locale (pt-BR)

Por padrão, o Angular adota o locale americano (en-US). Para que o CurrencyPipe e o DatePipe utilizem o padrão brasileiro com separador de vírgula e formato monetário em reais, é mandatório registrar os dados de cultura no app.config.ts:

// app.config.ts
import { ApplicationConfig, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localePt from '@angular/common/locales/pt';

// Registro oficial do locale no runtime
registerLocaleData(localePt);

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: LOCALE_ID, useValue: 'pt-BR' }
  ]
};

🛠️ 3. Construção de Custom Pipes Standalone

Para transformações de negócio específicas (como formatação de CPF/CNPJ, máscara de cartão, conversão de bytes ou cálculo de tempo decorrido relativo), cria-se um Custom Pipe implementando a interface PipeTransform:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'cpfMask',
  standalone: true,
  pure: true // Memoizado: reexecuta apenas se o valor de entrada mudar
})
export class CpfMaskPipe implements PipeTransform {
  transform(valor: string | number | null | undefined): string {
    if (!valor) return '';
    const digits = String(valor).replace(/\D/g, '');
    if (digits.length !== 11) return digits; // Retorna original se inválido

    return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
  }
}

3.1 Custom Pipe Parametrizado: Formatação de Tamanho de Arquivos (Bytes)

@Pipe({
  name: 'fileSize',
  standalone: true,
  pure: true
})
export class FileSizePipe implements PipeTransform {
  transform(bytes: number | null | undefined, decimais: number = 2): string {
    if (bytes == null || isNaN(bytes) || bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    const valor = parseFloat((bytes / Math.pow(k, i)).toFixed(decimais));
    return `${valor} ${sizes[i]}`;
  }
}

Uso no template:

<span>{{ 10485760 | fileSize:1 }}</span> <!-- Renderiza: 10.0 MB -->

⚖️ 4. Comparativo de Design: Pure Pipes vs Impure Pipes

Dimensão Técnica Pure Pipe (pure: true) Impure Pipe (pure: false)
Comportamento Padrão Sim (@Pipe({ pure: true })) Não, requer configuração explícita
Gatilho de Reavaliação Apenas quando os argumentos primitivos ou a referência do objeto mudam A cada ciclo de Change Detection (a cada clique, timer ou requisição)
Impacto de Desempenho Levíssimo (Memoização instantânea em O(1)) Crítico se mal utilizado (pode travar a thread de UI)
Casos de Uso Válidos 99% das formatações: moedas, datas, máscaras, slugs Streams assíncronas internas, inspeção de mutações profundas em coleções
Exemplo Nativo DatePipe, CurrencyPipe, DecimalPipe AsyncPipe, JsonPipe (quando inspeciona objetos mutados internamente)

⚠️ 5. Armadilhas Comuns e Anti-Patterns

  1. Criar Pipes Impuros para Filtrar Listas:
    • Anti-Pattern: Criar um filterPipe impuro para filtrar um array de 1.000 itens dentro do template com *ngFor ou @for. O pipe roda centenas de vezes a cada microssegundo.
    • Correção Padrão Ouro: Realizar filtros no TypeScript utilizando computed() do Angular Signals. O signal é calculado apenas quando o termo de busca ou a lista mudam.
  2. Encadear Múltiplos Pipes Pesados sem Critério:
    • Risco: {{ dado | pipeComplexoA | pipeComplexoB | pipeComplexoC }}.
    • Correção: Se a pipeline de transformação for custosa, centralize a lógica no modelo de dados ou pré-processe no Store/Signal.
  3. Esquecer de Adicionar o Pipe aos imports do Componente Standalone:
    • Erro: O Angular emitirá o erro NG8004: No pipe found with name 'cpfMask'.
    • Correção: Declarar a classe do pipe no array imports: [CpfMaskPipe] do @Component.

🛡️ 6. Boas Práticas e Segurança Corporativa

  • Sanitização de HTML: Nunca utilize um pipe para concatenar strings HTML não sanitizadas direto no [innerHTML] sem passar pelo DomSanitizer do Angular, evitando ataques XSS.
  • Tipagem Estrita em transform(): Tipar explicitamente o primeiro argumento e todos os parâmetros opcionais com tipos do TypeScript, tratando sempre entradas null e undefined.
  • Testabilidade Desacoplada: Pipes são classes TypeScript comuns com o método transform(). Eles podem ser testados unitariamente de forma instantânea sem precisar de TestBed.

📋 7. Cheat-Sheet: Resumo Rápido

// Declaração de Pipe Standalone
@Pipe({ name: 'meuPipe', standalone: true, pure: true })
export class MeuPipe implements PipeTransform {
  transform(val: string, arg1: number = 0): string {
    return val.substring(0, arg1);
  }
}

// Template
// {{ texto | meuPipe:10 }}
// Encadeamento:
// {{ valor | currency:'BRL' | lowercase }}