Como aplicar uma máscara de telefone, CPF, IP e data com ngx-mask
Aprenda a aplicar uma máscara de telefone, CPF, entre outros, em seus input's utilizando ngx-mask com Angular.

Quando falamos em formulários web, a entrada de dados deve sempre ser tratada. Todo e qualquer dado fornecido por um usuário deve ser sanitizado, tratado.
Desde números de telefone até datas e códigos de cartão de crédito, garantir a precisão e consistência dessas informações deve ser uma das prioridades do projeto. As máscaras em inputs surgem como uma solução elegante para ajudar nesse problema.
Em termos simples, uma máscara é como uma camada de orientação para o seu formulário. Ela define o formato esperado para a entrada de dados, guiando o usuário durante o preenchimento. Isso não apenas facilita a vida do usuário, mas também ajuda a prevenir erros comuns.

Utilizando o ngx-mask em um projeto Angular
Em projetos Angular, podemos recorrer a uma biblioteca chamada ngx-mask. Este pacote simplifica a aplicação de máscaras em inputs. Ele é particularmente útil quando se trata de formatar e validar dados de entrada, como números de telefone, datas, CPFs, entre outros, em formulários web.
Para demonstrar o uso da ngx-mask
vou criar um projeto do zero utilizando as seguintes versões:
$ ng version
Angular CLI: 16.2.3
Node: 18.18.2
Package Manager: npm 9.8.1
OS: darwin arm64
No comando abaixo utilizei o --skip-git
para não criar um repositório GIT local e usei o --minimal
para não criar uma estrutura para testes unitários, já que o projeto será utilizado apenas para estudo:
$ ng new --skip-git --minimal utilizando-ngx-mask
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
ng new
acesse este link ou digite ng new help
no seu terminal.Como o exemplo será construído utilizando Angular 16, a versão do ngx-mask
deve ser a 16
conforme indicado na documentação oficial:
$ npm install --save [email protected]
Com o único objetivo de melhorar o layout, também adicionei o bootstrap
na lista de dependências para utilizar algumas classes CSS:
$ npm i bootstrap
Configurando o projeto para usar ngx-mask
Com o projeto já criado e as dependências instaladas, para começar a utilizar o ngx-mask, importe o NgxMaskDirective
e registre o provideNgxMask()
no arquivo app.module.ts
conforme a seguir:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
NgxMaskDirective,
// Também incluí o módulo abaixo
// porque vamos trabalhar com o FormGroup
// ao longo do texto
ReactiveFormsModule,
],
providers: [provideNgxMask({ /* opções de cfg */ })],
bootstrap: [AppComponent],
})
export class AppModule {}
app.module.ts
Para conferir se a configuração está correta, alterei o AppComponent
conforme a seguir:
import { Component } from "@angular/core";
@Component({
selector: "app-root",
templateUrl: "app.component.html",
})
export class AppComponent {}
app.component.ts
<div class="container">
<div class="row">
<div class="col">
<form>
<h1>Formulário de cadastro</h1>
<div class="mb-3">
<label for="input-cpf" class="form-label">
CPF
</label>
<input
autofocus
class="form-control"
id="input-cpf"
inputmode="numeric"
mask="000.000.000-00"
placeholder="CPF"
type="text" />
</div>
<div class="mb-3">
<label for="input-telefone" class="form-label">
Telefone
</label>
<input
class="form-control"
id="input-telefone"
mask="(00) 0 0000 0000"
prefix="+55 "
type="tel" />
</div>
<div class="mb-3">
<label for="input-ip" class="form-label">
IP
</label>
<input
class="form-control"
id="input-ip"
inputmode="numeric"
mask="IP" />
</div>
<div class="mb-3">
<label for="input-data" class="form-label">
Data
</label>
<input
class="form-control"
id="input-data"
inputmode="numeric"
mask="d0/M0/0000" />
</div>
</form>
</div>
</div>
</div>
app.component.html
Resultado:

O básico do ngx-mask
Uma vez configurado o ngx-mask, podemos utilizar o atributo mask
nos inputs para configurar a máscara. Por exemplo:
(00) 0 0000 0009
│ │ │ │ │
│ │ │ └►└► patterns
│ │ │
└─►└───►└────► special characters
O (
, )
e espaço
são special characters e o 0
e 9
são patterns. Isto significa que quando o usuário digitar algo, os special characters serão mantidos no input e o 0
e 9
substituídos pelos números que o usuário digitar, sendo que 9
é um número opcional.
Segundo a documentação, por padrão a lista de special characters é a seguinte:
- / ( ) . : space + , @ [ ] " '
Novamente, os special characters são valores fixos no seu input, são valores que o usuário não pode alterar, por exemplo, o preenchimento da máscara [0] (0)-0
pode ser [3] (4)-5
.
Lista dos patterns:
0
: somente número9
: somente número, porém, opcionalA
: letra (maiúscula ou minúscula) ou númeroS
: somente letra (minúscula ou maiúscula)U
: somente letra maiúsculaL
: somente letra minúscula
Além dessa lista de patterns, há outras opções como por exemplo:
IP
- aplica uma máscara e valida o valor digitadopercent
- utilizado para porcentagemCPF_CNPJ
- aplica uma máscara para CPF ou CNPJHh:m0:s0
- máscara para hora (24h), minuto e segundod0/M0/0000
- máscara para data, mês e ano
A lista completa pode ser acessada neste link.
Integração com ReactiveFormsModule
Por padrão do ngx-mask, caso algum input não esteja preenchido com a máscara corretamente, o FormControl associado ao input é invalidado. Essa opção pode ser controlada através do atributo [validation]="true | false"
(sendo o valor padrão true
).
No exemplo a seguir, criei um formulário que utiliza a máscara em todos os inputs. Somente no primeiro input, CPF, deixei o [validation]="false"
para demonstrar esse comportamento. Observe o código e o resultado a seguir:
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup;
get cpf() {
return this.form.get('cpf')!;
}
get telefone() {
return this.form.get('telefone')!;
}
get ip() {
return this.form.get('ip')!;
}
get data() {
return this.form.get('data')!;
}
constructor(formBuilder: FormBuilder) {
this.form = formBuilder.nonNullable.group({
cpf: [''],
telefone: [''],
ip: [''],
data: [''],
});
}
}
app.component.ts
<div class="container">
<div class="row">
<div class="col">
<form [formGroup]="form">
<h1>Formulário de cadastro</h1>
<div class="mb-3">
<label for="input-cpf" class="form-label">CPF</label>
<input
[class.is-invalid]="cpf.touched && cpf.invalid"
[validation]="false"
autofocus
class="form-control"
formControlName="cpf"
id="input-cpf"
mask="000.000.000-00"
placeholder="CPF"
type="text" />
</div>
<div class="mb-3">
<label for="input-telefone" class="form-label">
Telefone
</label>
<input
[class.is-invalid]="telefone.touched && telefone.invalid"
class="form-control"
formControlName="telefone"
id="input-telefone"
mask="(00) 0 0000 0000"
prefix="+55 "
type="tel" />
</div>
<div class="mb-3">
<label for="input-ip" class="form-label">IP</label>
<input
[class.is-invalid]="ip.touched && ip.invalid"
class="form-control"
formControlName="ip"
id="input-ip"
inputmode="numeric"
mask="IP" />
</div>
<div class="mb-3">
<label for="input-data" class="form-label">Data</label>
<input
[class.is-invalid]="data.touched && data.invalid"
class="form-control"
formControlName="data"
id="input-data"
inputmode="numeric"
mask="d0/M0/0000" />
</div>
<button
[disabled]="form.invalid"
type="submit"
class="btn btn-primary">
Enviar
</button>
</form>
<div class="border border-primary m-4">
<pre class="p-4"><code>Valor do formulário (form.value):
{{form.value | json }}</code></pre>
</div>
</div>
</div>
</div>
app.component.html
{{ form.value | json }}
imprime o valor do formulário na tela.
Como resultado, observe que não preenchemos completamente os campos CPF nem Telefone; no entanto, apenas o campo Telefone ficou inválido. Isso ocorre porque configuramos no ngx-mask para que o FormControl vinculado ao CPF não seja considerado inválido caso a máscara não tenha sido totalmente preenchida.
Obtendo o valor com a máscara
Quando aplicamos a máscara 000.000.000-00
a um input e o vinculamos a um FormControl, após o usuário digitar o valor no input, ele visualizará na tela algo como 123.123.123-12
. Entretanto, o valor obtido no FormControl.value
será 12312312312
, ou seja, por padrão, os caracteres especiais (special characters) não são incluídos no FormControl.value
.
Em alguns cenários, como por exemplo, IP e Data, é interessante ter o valor completo, ou seja, com os pontos e barras. A boa notícia é que a ngx-mask já oferece essa opção através do atributo [dropSpecialCharacters]="true | false"
(default: true
).
Para demonstrar, utilizei o código HTML mencionado um pouco mais acima e adicionei o [dropSpecialCharacters]="false"
no campo IP."
<div class="mb-3">
<label for="input-ip" class="form-label">IP</label>
<input
[class.is-invalid]="ip.touched && ip.invalid"
[dropSpecialCharacters]="false"
class="form-control"
formControlName="ip"
id="input-ip"
inputmode="numeric"
mask="IP" />
</div>
trecho do app.component.html

Veja na parte inferior da imagem que o valor do cpf
contém apenas números e o valor do ip
tem os números e os pontos.
Formulário com validação do CPF, telefone, IP e data com máscaras
Por fim, adicionei algumas validações no formulário, sendo algumas nativas e outras customizadas (ver arquivo app.validators.ts
abaixo). Além disso, fiz pequenas modificações no HTML para exibir de forma amigável algumas mensagens de erro. Veja a seguir o código fonte:
import {
AbstractControl,
ValidationErrors,
ValidatorFn,
} from '@angular/forms';
export class AppValidators {
static isCPF(
control: AbstractControl
): ValidationErrors | null {
// Remover caracteres não numéricos
const cpf = control.value.replace(/\D/g, '');
// Verificar se o CPF tem 11 dígitos
if (cpf.length !== 11) {
return { isCPF: true };
}
// Verificar se todos os dígitos são
// iguais (evitar CPFs como 111.111.111-11)
if (/^(\d)\1+$/.test(cpf)) {
return { isCPF: true };
}
// Calcular os dígitos verificadores
let soma = 0;
for (let i = 0; i < 9; i++) {
soma += parseInt(cpf.charAt(i)) * (10 - i);
}
let resto = 11 - (soma % 11);
if (resto === 10 || resto === 11) {
resto = 0;
}
if (resto !== parseInt(cpf.charAt(9))) {
return { isCPF: true };
}
soma = 0;
for (let i = 0; i < 10; i++) {
soma += parseInt(cpf.charAt(i)) * (11 - i);
}
resto = 11 - (soma % 11);
if (resto === 10 || resto === 11) {
resto = 0;
}
if (resto !== parseInt(cpf.charAt(10))) {
return { isCPF: true };
}
return null;
}
static minDate(min: Date): ValidatorFn {
return (
control: AbstractControl
): ValidationErrors | null => {
const [dd, mm, yyyy] = control.value
.split('/')
.map((a: string) => a || '0')
.map(Number);
const date = new Date(yyyy, mm - 1, dd);
if (date.getTime() > min.getTime()) {
return null;
}
return { minDate: min };
};
}
}
app.validators.ts
app.validators.ts
, demonstra como podemos criar validações customizadas. Estas função podem ser usadas na configuração do FormGroup
. Inclusive isso já foi tema por aqui: https://consolelog.com.br/como-validar-um-intervalo-de-data-utilizando-custom-validator-reactiveforms-angular2/import { Component } from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators,
} from '@angular/forms';
import { AppValidators } from './app.validators';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup;
get cpf() {
return this.form.get('cpf')!;
}
get telefone() {
return this.form.get('telefone')!;
}
get ip() {
return this.form.get('ip')!;
}
get data() {
return this.form.get('data')!;
}
constructor(formBuilder: FormBuilder) {
this.form = formBuilder.nonNullable.group({
cpf: ['', [Validators.required, AppValidators.isCPF]],
telefone: ['', [Validators.required]],
ip: ['', [Validators.required]],
data: [
'',
[
Validators.required,
AppValidators.minDate(
new Date('2023-11-01T00:00:00.000-0300')
),
],
],
});
}
}
app.component.ts
<div class="container">
<div class="row">
<div class="col">
<form [formGroup]="form">
<h1>Formulário de cadastro</h1>
<div class="mb-3">
<label for="input-cpf" class="form-label">CPF</label>
<input
[class.is-invalid]="cpf.touched && cpf.invalid"
autofocus
class="form-control"
formControlName="cpf"
id="input-cpf"
mask="000.000.000-00"
placeholder="CPF"
type="text" />
<div
*ngIf="cpf.errors?.['isCPF']"
class="invalid-feedback">
Informe um CPF válido
</div>
<div
*ngIf="cpf.errors?.['required']"
class="invalid-feedback">
Por favor, informe um CPF.
</div>
</div>
<div class="mb-3">
<label for="input-telefone" class="form-label">
Telefone
</label>
<input
[class.is-invalid]="
telefone.touched && telefone.invalid
"
class="form-control"
formControlName="telefone"
id="input-telefone"
mask="(00) 0 0000 0000"
prefix="+55 "
type="tel" />
<div
*ngIf="telefone.errors?.['required']"
class="invalid-feedback">
Por favor, informe um número de telefone.
</div>
</div>
<div class="mb-3">
<label for="input-ip" class="form-label">IP</label>
<input
[class.is-invalid]="ip.touched && ip.invalid"
[dropSpecialCharacters]="false"
class="form-control"
formControlName="ip"
id="input-ip"
inputmode="numeric"
mask="IP" />
<div
*ngIf="ip.errors?.['required']"
class="invalid-feedback">
Por favor, informe um IP.
</div>
</div>
<div class="mb-3">
<label for="input-data" class="form-label">Data</label>
<input
[class.is-invalid]="data.touched && data.invalid"
[dropSpecialCharacters]="false"
class="form-control"
formControlName="data"
id="input-data"
inputmode="numeric"
mask="d0/M0/0000" />
<div
*ngIf="data.errors?.['required']"
class="invalid-feedback">
Por favor, informe uma data
</div>
<div
*ngIf="data.errors?.['minDate']"
class="invalid-feedback">
Por favor, informe uma data maior que
{{data.errors?.['minDate'] | date : 'dd/MM/yyyy'}}
</div>
</div>
<button
[disabled]="form.invalid"
type="submit"
class="btn btn-primary">
Enviar
</button>
</form>
<div class="border border-primary m-4">
<pre class="p-4"><code>Valor do formulário (form.value):
{{form.value | json }}</code></pre>
</div>
</div>
</div>
</div>
app.component.html
Resultado final:

Um último ponto que vale comentar é que, por padrão, as validações são aplicadas no evento onBlur, ou seja, quando o usuário clica no input e depois sai. Se quiser modificar este comportamento e validar conforme o usuário digita, utilize a opção updateOn
na configuração do FormControl.
Considerações
Ao incorporar máscaras nos inputs, proporcionamos não apenas uma diretriz visual para os usuários, mas também estabelecemos um mecanismo que promove a consistência dos dados. Essa abordagem não só simplifica o processo de preenchimento, mas também desempenha um papel crucial na prevenção de erros comuns.
Além disso, a ngx-mask, integrada ao Angular, destaca-se por sua flexibilidade e facilidade de implementação. Seja lidando com formatos específicos de data, exigências precisas para números de telefone ou padrões específicos para códigos, a biblioteca oferece uma gama de opções configuráveis.
Links para documentação: