Como construir um pipe para formatar CPF - Angular 2+

Processar informações para exibi-lás em um determinado formato, com certeza é uma das tarefas mais comuns para desenvolvedores. Raramente veremos um número de telefone sendo exibido na tela como 55119888888888 mas sim como +55 11 9 88888-8888. Falando de moeda, não vamos exibir na tela um valor bruto como 1.45 mas sim R$ 1,45.

Este processamento com foco na formatação/transformação da informação, pode ser feito através da utilização dos Pipes no contexto do Angular. Para deixar as coisas mais claras, vamos utilizar como exemplo um pipe nativo do Angular para deixar um texto com letras maiúsculas:

<div>{{'meu texto' | uppercase}}</div>

O resultado será: <div>MEU TEXTO</div>

Nativamente temos os seguintes pipes prontos para uso:

Link da documentação oficial dos pipes acima: https://angular.io/guide/pipes

Como construir um pipe customizado

Para a construção do nosso estudo, vamos supor que os valores dos CPFs chegarão ao frontend em formato numérico ou texto, por exemplo:

[
    '11122233344',
    11122233344,
    1122233344
]

Nosso objetivo é formatar estes valores de modo a sempre exibir o seguinte: 999.999.999-99. Caso o número do CPF seja menor que 11, complementaremos com zeros á esquerda.

Construção do pipe

Para construir um pipe é necessário criar uma classe e implementar o PipeTransform conforme o exemplo abaixo:

// cpf.pipe.ts

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

@Pipe({ name: 'cpf' })
export class CpfPipe implements PipeTransform {
    transform(value: string|number): string {
        return value + 'teste';
    }
}

Olhando o código acima, o método transform receberá um valor e retornará uma string com a palavra 'teste' concatenada no final. Mais a frente iremos desenvolver a lógica de formatação do CPF.

Após criar o arquivo cpf.pipe.ts temos que declará-lo em algum módulo. No nosso cenário de testes vamos registrar este novo pipe no AppModule, arquivo app.module.ts, que já tem registrado o componente que iremos utilizar para efetuar os testes, app.component.ts:

// app.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CpfPipe } from './cpf.pipe';

@NgModule({
  declarations: [
    AppComponent, // <<< Componente que irá utilizar o pipe
    CpfPipe,      // <<< Declaração do pipe
  ],
  imports: [
    AppRoutingModule,
    BrowserModule,
    CommonModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
// app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  itens = [
    '11122233344',
    11122233344,
    1122233344
  ];
}
<!-- app.component.html -->
<html>
  <head>
    <title>Testes</title>
  </head>
  <body>
    <ul>
      <li *ngFor="let item of itens">
          {{item | cpf}}
      </li>
    </ul>
    <router-outlet></router-outlet>
  </body>
</html>

Após executar a aplicação (ng serve) podemos ver o resultado na tela do navegador:

Resultado da utilização do CpfPipe

Então até este ponto nosso pipe está funcionando. Agora vamos começar a desenvolver a lógica para formatar o CPF.

Construindo um pipe para formatar CPF

A lógica de formatação ficará dentro do arquivo cpf.pipe.ts. Vamos construir a lógica da seguinte forma:

  1. Caso o tamanho do valor seja menor que 11, completaremos com zeros à esquerda.
  2. Caso o tamanho da string seja maior que 11, vamos pegar apenas até a décima primeira posição.
  3. Vamos retirar do valor qualquer coisa que não seja um número. Então se chegar algo como 111-22233344 o resultado será 11122233344
  4. Vamor extraír 3 grupos de 3 dígitos e depois 1 grupo de 2 dígitos para formatar o CPF conforme o seguinte: (3 dígitos).(3 dígitos).(3 dígitos)-(2 dígitos):
// cpf.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'cpf' })
export class CpfPipe implements PipeTransform {
    transform(value: string|number): string {
        let valorFormatado = value + '';

        valorFormatado = valorFormatado
            .padStart(11, '0')                  // item 1
            .substr(0, 11)                      // item 2
            .replace(/[^0-9]/, '')              // item 3
            .replace(                           // item 4
                /(\d{3})(\d{3})(\d{3})(\d{2})/,
                '$1.$2.$3-$4'
            );

        return valorFormatado;
    }
}

Ao salvar o arquivo acima, nossa página será recarregada com o seguinte resultado:

Resultado da utilização do CpfPipe com a formatação desejada

Agora que a formatação do CPF já está OK, vamos avançar nos estudos e ver como podemos passar parâmetros para o nosso pipe para por exemplo, escolhar mascarar (ocultar) ou não o número do CPF.

Adicionando uma opção para máscarar alguns números do CPF

Repare que o método transform do arquivo cpf.pipe.ts recebe apenas um valor. Para receber um segundo valor podemos adicionar um novo parâmetro, ocultarAlgunsValores,  e passar o valor para o método separando os argumentos por : no template conforme o código abaixo:

// cpf.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'cpf' })
export class CpfPipe implements PipeTransform {
    transform(value: string|number,
              ocultarAlgunsValores: boolean = false): string {
        let valorFormatado = value + '';

        valorFormatado = valorFormatado
            .padStart(11, '0')
            .substr(0, 11)
            .replace(/[^0-9]/, '')
            .replace(
                /(\d{3})(\d{3})(\d{3})(\d{2})/,
                '$1.$2.$3-$4'
            );

        if (ocultarAlgunsValores) {
            valorFormatado =
                'XXX.' + valorFormatado.substr(4, 7) + '-XX';
        }

        return valorFormatado;
    }
}
<!-- app.component.html -->

<!-- ... (código ocultado) ... -->

<li *ngFor="let item of itens">
    {{item | cpf : true}}
</li>

<!-- ... (código ocultado) ... -->

Resultado da renderização:

Resultado da utilização do CpfPipe com a opção ocultarAlgunsValores = true

Considerações

O pipe é um recurso extremamente útil para formatar ou transformar informações no seu template. É um recurso simples de ser implementado e muito útil, inclusive para questões de performance que serão abordadas em uma outra hora.

Links interessantes: