Entendendo o pure e standalone do Angular Pipe
Veja as diferenças de valores nos parâmetros pure e standalone de um Angular Pipe. Aprenda a usá-los para melhorar o desempenho e a reutilização.
Em Angular, um "pipe" é um recurso que permite transformar dados antes de serem exibidos na interface do usuário. Por exempo, pode ser utilizado para formatar valores tornando a exibição do dado mais legível. Também aumenta o reaproveitamento de código e traz vantagens em performance.
Por aqui este assunto já foi abordado, então neste texto gostaria de aprofundar alguns detalhes sobre os pipes, falar sobre o parâmetro pure
, standalone
e construir alguns exemplos falando um pouco sobre value type e reference type do JavaScript.
Criando ambiente de estudo
Os exemplos criados neste texto foram executados com as seguintes configurações do Node.js e Angular:
Para criar o projeto:
$ ng new pipes --skip-tests --skip-git
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
Para testar o projeto recém-criado:
$ cd pipes
$ npm run start
Agora que o ambiente está configurado vamos falar um pouco sobre os pipes.
Um pouco sobre os pipes
Um pipe é um código que você pode usar para transformar o valor antes de exibi-lo na tela. Normalmente é utilizado direto nos templates para transformar valores, por exemplo, formatar um valor numérico para uma representação monetária.
A síntaxe para utilizar um pipe é o caractere |
. Ele é adicionado após o valor que deseja-se transformar, por exemplo, {{ 'OLÁ' | lowercase }}
será transformado para olá
. Quando o pipe tem argumentos podemos informá-los após a declaração do pipe separando por :
, por exemplo: {{ 10 | currency : 'USD' }}
.
Os pipes podem ser encadeados para realizar várias transformações em sequência, por exemplo, {{ [0,1,2,3,4,5] | slice : 1 : 3 | json }}
será transformado para [ 1, 2 ]
. Neste exemplo o pipe slice
pegou uma parte do array de entrada, resultando no array [1, 2]
e este resultado passou pelo pipe json
para transformar este array em uma string (JSON).
Exemplo - construindo o FiltroPipe
Agora que falamos o básico sobre os pipes, vamos partir para uma implementação. Vamos criar um pipe que irá filtrar uma lista de nomes. A regra é simples, o pipe deve retornar uma lista filtrada apenas com os nomes que iniciam com algum valor, por exemplo, "filtre os nomes que começam com ab
".
Para construir um pipe temos que implementar a interface PipeTransform
. Então podemos escrever o seguinte código:
Na sequência temos o módulo que declara e exporta o pipe acima:
Para utilizar o FiltroPipe
basta importar o módulo FiltroModule
no módulo que declara o componente que utilizará o filtro. No nosso exemplo será o AppModule
que contém o AppComponent
:
Observação: importei o FormsModule
para utilizar o [(ngModel)]
no template (app.component.html).
No AppComponent
declaramos uma lista de nomes e uma variável filtro
. O objetivo é exibir a lista e permitir que o usuário execute um filtro:
Resultado:
Agora que construímos um pipe funcional, vamos falar sobre o standalone
e pure
, que são parâmetros na declaração do @Pipe({})
.
standalone
Este recurso foi introduzido no Angular 14 e tem por objetivo eliminar a necessidade de declarar o componente ou pipe em um módulo (NgModule
). Antes deste recurso era necessário declarar um componente ou pipe obrigatoriamente em um módulo e eventualmente este módulo só continha uma única declaração. Este padrão é conhecido como SCAM - Single Component Angular Module e é exatamente o que fizemos com o FiltroModule
(mais acima).
O standalone
dispensa a necessidade de declararmos um módulo exclusivo para um componente ou pipe, então no nosso exemplo podemos fazer o seguinte:
- excluir o
FiltroModule
- incluir o parâmetro
standalone: true
noFiltroPipe
- alterar o
AppModule
para importar oFiltroPipe
ao invés doFiltroModule
No AppModule
retiramos o FiltroModule
e colocamos o FiltroPipe
:
Angular pipes marked asstandalone
do not need to be declared in an NgModule. Such pipes don't depend on any "intermediate context" of an NgModule (ex. configured providers).
https://angular.io/api/core/Pipe
pure
O parâmetro pure
recebe um valor booleano que por padrão é true
. Este valor tem relação com a frequência com que a transformação de dados é realizada. Por exemplo, um "pipe puro" é executado apenas quando há uma mudança nos dados de entrada, já o "impuro" é executado sempre que o ciclo de detecção de mudanças do Angular é ativado.
Quando ocorre algum evento que possa atualizar seu data model o CD entra em cena. Por padrão os eventos que disparam este processo são:
todos os eventos do navegador (click, mouseover, keyup, etc.)setTimeout()
esetInterval()
Requisições HTTP (Ajax)
https://consolelog.com.br/como-funciona-change-detection-angular/
No nosso exemplo não declaramos o parâmetro pure
, portanto o valor assumido é true
. Desta forma o método transform
(dentro do FiltroPipe
) só será executado caso algum parâmetro (do método transform
) seja alterado. Então conforme o usuário digitar no input, o valor da variável filtro
será modificado, portanto o transform
será chamado e emitirá uma mensagem no console confome o GIF abaixo:
Veja que o evento onblur
(ocorre quando o input de filtro tem o foco e o usuário clica em outro lugar fazendo com que o foco saia do input) e o clique no botão "Dispara um evento qualquer" não dispara a execução do método transform
do FiltroPipe
. Isto ocorre porque não houveram alterações na lista
e filtro
, que são os valores passados para o FiltroPipe
.
Alterando o valor pure: true
para pure: false
no FiltroPipe
temos o seguinte resultado:
Veja que quando o usuário digita algo no input o transform
do FiltroPipe
é executado, exatamente como antes, porém quando disparamos algum outro evento não relacionado aos parâmetro que o FiltroPipe
recebe, por exemplo, clicando em um botão ou apenas disparando o evento onblur
, ainda assim o código do pipe é executado, ou seja, sempre que ocorre a execução de detecção de mudanças do Angular.
Observação: os exemplos acima foram executados em modo production
. Executando os testes em desenvolvimento você perceberá uma duplicidade nas mensagens printadas no console devido a dupla checagem que o Angular faz durante o ciclo de detecção de mudanças.
This guarantee comes at the expense of Angular always running change detection twice, the second time for detecting this type of cases. In production mode change detection is only run once.
https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
Após entender a diferença de comportamento entre o "puro" e "impuro" você pode se perguntar onde deveria utilizar um pipe pure: false
. Bom, em alguns cenários um pipe "impuro" pode ser útil, por exemplo, em um filtro envolvendo a data corrente ou requisições à APIs. Nestes dois casos os parâmetros de entrada são exatamente os mesmos, mas podem produzir resultados diferentes.
Resumindo, os pipes "puros" são mais recomendados porque podem aproveitar o cache de resultados do Angular, reduzindo a frequência de cálculos e, portanto, melhorando o desempenho do aplicativo. Os pipes "impuros" (pure: false
) podem ser úteis em situações em que é necessário atualizar a exibição com frequência, mas deve-se usá-los com cuidado para não prejudicar a performance do aplicativo.
When true, the pipe is pure, meaning that thetransform()
method is invoked only when its input arguments change. Pipes are pure by default.
https://angular.io/api/core/Pipe
Atenção com primitive type vs reference type
Em JavaScript temos os tipos primitivos e não primitivos. Os tipos string
, number
, bigint
, boolean
, undefined
, symbol
e null
são tipos primitivos. Isto significa que o valor é armazenado diretamente na variável. Por exemplo, quando você atribui uma string a uma variável, o valor da string é armazenado na posição de memória associada a essa variável. Quando você atribui uma nova string a essa mesma variável, a posição de memória é atualizada com o novo valor da string.
All primitives are immutable; that is, they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned to a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered. The language does not offer utilities to mutate primitive values.
https://developer.mozilla.org/en-US/docs/Glossary/Primitive
Já no caso dos tipos não primitivos, como objetos, arrays e funções, o valor é armazenado por referência. Isso significa que, em vez de armazenar o valor diretamente na variável, o JavaScript armazena um ponteiro para a posição de memória onde o valor está armazenado. Quando você atribui uma nova referência a uma variável, a posição de memória associada a essa variável é atualizada com o novo ponteiro para o objeto. Isso significa que, mesmo que você atribua uma nova referência a uma variável, o objeto original ainda existe na memória e pode ser acessado por outras variáveis que o referenciam.
// Case 1: Evaluation result is the same as using ===
Object.is(25, 25); // true
Object.is("foo", "foo"); // true
Object.is("foo", "bar"); // false
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(window, window); // true
Object.is([], []); // false
const foo = { a: 1 };
const bar = { a: 1 };
const sameFoo = foo;
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(foo, sameFoo); // true
// Case 2: Signed zero
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(-0, -0); // true
// Case 3: NaN
Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true
// fonte:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
Trazendo as considerações acima para a construção de um pipe, veja como o pure
pode modificar o comportamento do pipe diante dos tipos primitivos e não primitivos:
Importando o ToJsonPipe
no AppModule
podemos utilizá-lo no template:
Já na classe temos dois métodos:
alterarValor
: altera uma propriedade deitens
e o valor da variávelvalor
manterValor
: mantém o valor da variávelvalor
como5
e uma propriedade deitens
com seu valor original declardo no escopo da função.
Veja no template que estamos aplicando o toJson
em um valor numérico (tipo primitivo) e um objeto chamado itens
(não primitivo). Observe o resultado considerando que estamos utilizando o pure: true
:
É possível notar que o valor da variável valor
é atualizado na tela sempre que recebe um novo valor, já que estamos utilizando o pure: true
. O mesmo não ocorre quando alteramos uma propriedade do objeto itens
. O motivo é que em JavaScript um object
é tratado como um tipo não primitivo, ou seja, um tipo referenciado. Então quando passamos o itens
para o ToJsonPipe
, no fundo estamos passando um poteiro, um endereço de memória que aponta para o conteúdo do object
. Quando o Angular compara os valores, identifica que não houve mudanças neste ponteiro, ou seja, ele ainda aponta para o mesmo objeto, portanto não atualiza o valor na tela. Para testar esta última afirmação você pode clonar o objeto itens
e verá que agora o transform
do seu pipe irá atualizar os valores da expressão {{ itens | toJson }}
:
Se optarmos por utilizar o pure: false
, como sabemos, o Angular irá sempre executar a função transform
quando houver um ciclo de atualização da tela:
Então sempre tome cuidado ao optar por um valor no parâmetro pure
do seu pipe.
Considerações
Um pipe é uma ferramenta útil em Angular que permite formatar e transformar dados de forma fácil e consistente. Ajuda na performance e no reaproveitamento de código. O uso correto do parâmetro pure
irá ajudar a tomar a melhor decisão levando em consideração performance, tipo do dado e frequência de atualização.
Também falamos sobre o standalone
e um pouco sobre o uso de dados primitivos e não primitivos. Deixo como sugestão analisar os pipes que o Angular disponibiliza nativamente. Esta lista de pipes pode ser encontrada neste link.
Links relacionados ao conteúdo abordado:
- Formatar data - Angular - DatePipe
- Formatar valor (moeda) em Angular 2+ CurrencyPipe
- Filtro de tabela utilizando PipeTransform e DebounceTime - como filtrar os dados em tempo real - Angular 2+
- Como construir um pipe para formatar CPF - Angular 2+
- Input Porcentagem - Angular
- https://angular.io/guide/pipes
- https://angular.io/api/core/Pipe
- https://developer.mozilla.org/en-US/docs/Glossary/Primitive