O Angular Signals fornecem uma maneira de você dizer para a aplicação que os dados mudaram.
Além de melhorar o desempenho do Angular e tornar o código mais reativo…
Por isso continue lendo esse artigo para aprender a usar essa funcionalidade incrível.
O que é Angular Signals?
O Signal é um valor reativo, isso significa que seu valor muda ao longo do tempo… e ele fica observando no caso de sofrer alguma alteração.
Por isso, ele não é apenas um valor que pode ser modificado…
Quando você realiza uma alteração, o Signal notifica qualquer ponto do código que faz uso dele. Permitindo que reajam à mudança.
E esses “pontos no código” são chamados de consumidores, como mostrado no esquema abaixo:
Isso faz do Signal o elemento central de uma aplicação reativa, pois permite que diferentes partes do sistema sejam atualizadas automaticamente em resposta a alterações nos dados:
Como criar um Signal?
Você pode criar um Signal com ajuda da função signal()
, que leva 2 argumentos:
initialValue:
o valor inicial;options:
um objeto do tipoCreateSignalOptions
que inclui um métodoequal
para comparar 2 valores. (opcional)
// Assinatura da função signal
export function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): WritableSignal<T>
// Assinatura de CreateSignalOptions
export interface CreateSignalOptions<T> {
/**
* Função de comparação que define a igualdade para valores de sinal.
*/
equal?: ValueEqualityFn<T>;
}
Perceba que, na assinatura do método, você pode definir o tipo de valor que o Signal vai lidar. (representado por T
)
Dessa forma, ao criar um Signal, teríamos algo parecido com isso:
const movies = signal<Movie[]>([{ name : "Titanic", year : 1997, ... }]);
E se o objeto options
não for fornecido, como no exemplo acima, a função defaultEquals
será usada em seu lugar.
Sendo que a função defaultEquals
compara 2 valores do mesmo tipo T
usando uma combinação do operador ===
e o método Object.is()
.
Como alterar o valor de um Signal?
Agora, note que a função signal()
não retorna um objeto Signal
, mas um WritableSignal<T>
…
Afinal, o Signal é uma função getter, então pode ser usado da seguinte maneira:
console.log(movies());
// Resultado: [{ name : "Titanic", year : 1997, ... }]
Porém o tipo WritableSignal
fornece a possibilidade de alterar o valor através de 3 métodos: set
, update
e mutate
.
1 – set()
Substitui o valor do Signal:
movies.set([{ name : "Matrix", year : 1999, ... }]);
console.log(movies());
// Resultado: [{ name : "Matrix", year : 1999, ... }]
2 – update()
Substitui o valor do Signal com base no valor atual. O que é particularmente útil quando o novo valor é calculado a partir do antigo:
numberMovies.update(number => number + 1);
Observação: O método update
usa o método set
por baixo dos panos para executar as atualizações.
3 – mutate()
Também atualiza o valor do Signal com base no valor atual:
movies.mutate(list => {
list.push({ name : "Pulp Fiction", year : 1994, ... });
});
console.log(movies());
/* Resultado:
* [
* { name : "Matrix", year : 1999, ... },
* { name : "Pulp Fiction", year : 1994, ... }
* ]
*/
Porém, diferente do update
, o método mutate
não retorna um novo valor… Ele modifica o antigo.
Mas qual o impacto disso?
Dessa forma, o update
permite você trabalhar com objetos imutáveis. Esses objetos são aplicados em algumas libs (por razões de desempenho) como o NGRX.
No caso do data binding baseado em Signals, é irrelevante qual função você vai usar.
Por último, depois de qualquer mudança em seu valor, o Signal notifica seus consumidores (ou dependentes)…
Finalmente, como usar o Angular Signals?
Vimos que o Signal é basicamente um variável que notifica seus “consumidores” quando sofre alguma alteração.
Sendo consumidor qualquer código que tenha registrado interesse no valor do Signal e deseja ser notificado sempre que o valor mudar.
Então, como podemos adicionar consumidores ao Signal?
Existem 3 formas: getter function, effect
, computed
.
1 – Getter Function
O jeito mais simples para você ler e usar o valor de um Signal é colocando 2 parênteses na frente dele:
movies();
Tecnicamente falando… Dessa forma estamos chamando uma getter function…
Além disso, você também pode utilizar esse método no template como se fosse uma variável normal:
{{ movies() }}
2 – effect()
Às vezes, quando o Signal tem um novo valor, é necessário executar alguma operação ou tarefa em outra parte da aplicação.
Esse tipo de lógica é chamada de efeito colateral (ou side effect) e podemos alcançar esse resultado através do método effect
:
export function effect(effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef
O effect
executa a função effectFn
sempre que o valor do Signal mudar:
effect(() => console.log(`Nós temos agora: ${movies().length} filmes`));
Perceba que colocamos o Signal dentro da função effectFn
. Assim a função é adicionada à lista de consumidores daquele Signal.
Além disso, múltiplos Signals podem ser adicionados…
Dessa forma, effectFn
será reexecutado com qualquer mudança que ocorra nos Signals chamados dentro dele:
effect(() => {
console.log(`Nós temos agora: ${movies().length} filmes`);
console.log(`E também: ${series().length} séries`);
});
Então, quando o Signal tiver um novo valor usando os métodos set, update ou mutate, o effectFn
será reexecutado com o valor atualizado.
3 – computed()
E se houver um Signal que depende do valor de outros Signals? E precisa ser recalculado sempre que uma dessas dependências mudar.
Nesse caso, você pode usar a função computed
para criar um novo Signal que:
- Calcula seu valor a partir de alguns Signals de entrada;
- E se atualiza automaticamente sempre que uma dessas entradas sinaliza alteração.
export function computed<T>(computation: () => T, equal: ValueEqualityFn<T> = defaultEquals): Signal<T>
Veja abaixo um exemplo de uso:
numberMovies = computed(() => movies().length);
No exemplo, qualquer Signal usado dentro do computed
será rastreado como dependência.
Além disso, assim como em effect
, múltiplos Signals podem ser adicionados…
numberShows = computed(() => movies().length + series().length);
Note que a função computed
retorna um objeto Signal
e não um WritableSignal
, isso significa que não pode ser modificado manualmente usando os métodos: set, update ou mutate.
Porém, o resultado ainda pode ser adicionado a novas operações de effect
e computed
como mostra o esquema abaixo:
Diferença entre Change Detection e Angular Signals
Se você não sabe como funciona o Change Detection no Angular, recomendo que dê uma olhada nesse guia.
Em resumo, quando o Angular detecta alguma mudança na aplicação, ele aciona o Change Detection para atualizar o estado do aplicativo.
Porém, quando esse mecanismo começa, o Angular passa por todos os componentes verificando se o estado de cada um mudou ou não.
Se você não achou esse processo otimizado, está certo.
Afinal, nem toda mudança afeta todos os componentes, mas ele verifica todos mesmo assim.
A ideia aqui não é explicar a fundo como funciona o Change Detection…
Mas explicar que o Angular Signals atualiza os componentes de maneira pontual, sem acionar a verificação em toda o sistema.
E esse controle mais fino sobre o Change Detection gera um melhor desempenho.
Conclusão
Particularmente, gostei muito do Angular Signals por:
- Ser fácil de usar;
- Ter uma escrita limpa e elegante;
- E possuir um bom desempenho.
Porém, cuidado para não se empolgar e sair utilizando ele em todos os lugares sem necessidade.
Análise cada caso e tenha um objetivo claro antes de adicionar os Signals ao seu código…
Ou então você vai apenas acrescentar mais complexidade ao sistema.
Por fim, se você gostou do artigo, ou ficou com alguma dúvida, não esqueça de deixar seu comentário abaixo:
Crie inputs personalizados no Angular com ControlValueAccessor
[Infográfico] 28 Melhores Extensões do Visual Studio Code para Angular
{ Angular LifeCycle Hooks } Controle sua Aplicação como um Mestre Jedi
[Infográfico] Angular LifeCycle: O Guia Rápido de Referências do Jovem Padawan
Hey,
o que você achou deste conteúdo? Conte nos comentários.
Referências:
freecodecamp
dev.to
angulararchitects
itnext
netbasal