Entendendo os Signals do Angular
Olá, pessoa!
Antes de começarmos com Angular Signals, você já se perguntou como as alterações feitas no seu estado se refletem na view?
Quando o seu estado muda, o Angular automaticamente aplica essas mudanças na view correspondente. Isso possibilita uma interação fluida e sincronizada entre model e view, proporcionando uma experiência suave pro usuário.
Mas acredita em mim, isso não é mágica!
A chave para essa sincronização é a detecção de mudanças e o Zone.js. Embora isso não seja o foco deste artigo, precisamos entender como o Angular lida com isso, assim o objetivo dos Signals vai ficar ainda mais claro no fim.
Aplicações Angular são construídas usando uma arquitetura baseada em componentes, onde cada componente representa uma funcionalidade específica. Nossa app, no fim das contas, é uma árvore de componentes.
Usando "zonas" e executando código dentro delas, o framework consegue perceber melhor as operações realizadas. Isso permite que ele trate os erros de forma mais eficaz e, o mais importante, dispare a detecção de mudanças sempre que algo acontece na aplicação (model muda, view atualiza). Então, podemos dizer que:
- O Zone.js está sempre atento e detecta quando algo acontece na aplicação;
- Quando alguma coisa acontece, o Zone.js dispara a detecção de mudanças para atualizar o estado da aplicação.
Quando a detecção de mudanças começa, o framework percorre todos os componentes na árvore para verificar se o estado deles mudou ou não e se o novo estado afeta a visualização. Se for o caso, a parte do DOM do componente que foi afetada pela mudança é atualizada.
Essa funcionalidade pode ter um impacto significativo no desempenho da aplicação, realizando trabalhos que podem não ser necessários, já que a maioria dos componentes pode não ser afetada pelo evento e ela sempre percorre toda a árvore.
Você deve estar se perguntando: "Mas Vinny, por que você está explicando isso? Este artigo não é sobre Angular Signals?"
O ponto é que os Signals podem nos ajudar a evitar essas verificações desnecessárias e atualizar apenas a parte da aplicação que mudou. E isso é muito importante. Muito mesmo.
Bom, agora que vimos como o Zone.JS trabalha, podemos seguir em frente e aprender sobre Angular Signals. Vamos lá, é hora de mergulhar nesse universo empolgante e descobrir como ele pode tornar nossas aplicações mais eficientes e reativas!
Angular Signals! O que são, afinal?
Os Signals são valores reativos, que tecnicamente são funções sem argumentos [(() => T)], que, ao serem executadas, retornam um valor. Podemos dizer que um Signal é um tipo especial de valor que pode ser observado para mudanças.
Como criamos um Signal? É simples: criamos e iniciamos um Signal chamando a função signal()
.
A função signal() recebe dois argumentos:
- initialValue: Representa o valor inicial do Signal, podendo ser de qualquer tipo T.
- options: É um objeto do tipo
CreateSignalOptions
, que inclui um métodoequal
para comparar dois valores do tipoT
. Se o objetooptions
não for fornecido, a funçãodefaultEquals
será usada. Esta função compara dois valores do mesmo tipo T, usando uma combinação dos operadores===
eObject.is
.
A função signal retorna um WritableSignal<T>
. Um Signal é uma função getter
, mas o tipo WritableSignal
nos dá a possibilidade de modificar o valor por três métodos:
set
[set(value: T): void] para substituição (define o novo valor e notifica as dependências):
jogos.set(['Baldur's Gate III'])
update
[update(updateFn: (value: T) => T)] para derivar um novo valor (atualiza o valor do Signal, com base em seu valor atual e notifica as dependências). A operação de atualização usa a operação set() para realizar atualizações por debaixo dos panos:
nivelPersonagem.update(nivel => nivel + 1)
Então, um Signal é um valor reativo que pode ser observado, atualizado e notificar quaisquer dependentes.
Mas o que significa 'notificar quaisquer dependentes'?
Aqui entra a parte fascinante do Signal. Vamos explorar o que são os dependentes para os Signals e como notificá-los.
Signal não é apenas um valor que pode ser modificado, é algo mais. Signal é um valor reativo e atua como um produtor que notifica os consumidores (dependentes) quando muda.
Então, os dependentes no Signal são qualquer código que registrou interesse no valor do Signal e deseja ser notificado sempre que o valor do Signal mudar. Quando o valor do Signal é modificado, ele notifica todos os seus dependentes, permitindo que reajam à mudança no valor do Signal. Isso torna o Signal um elemento central de uma aplicação reativa, pois permite que diferentes partes da aplicação atualizem automaticamente em resposta às mudanças nos dados.
E como podemos adicionar dependentes (consumidores) ao Signal?
Podemos adicionar consumidores usando as funções effect e computed.
effect
Às vezes, quando um signal recebe um novo valor, podemos precisar adicionar um efeito colateral. Para isso, usamos a função effect().
O effect
agenda e executa uma função com efeitos colaterais dentro de um contexto reativo.
Como usar o effect
A função dentro do effect
será reavaliada com qualquer mudança que ocorra nos signals chamados dentro dela. Múltiplos signals podem ser adicionados à função effect
.
effect(() => {
console.log(`Temos um total de: ${jogos().length} jogos cadastrados.`);
});
Vamos explorar o funcionamento por trás da função effect:
Quando declaramos uma função effect
, a função(effectFn) passada como argumento será adicionada à lista de consumidores de quaisquer signals usados, como jogos
em nosso exemplo. (Signals usados pela effectFn serão os produtores).
Então, quando o signal recebe um novo valor usando os operadores set
ou update
a effectFn
será reavaliada com o novo valor do signal (o produtor notifica todos os consumidores dos novos valores).
A função effect
retorna um EffectRef
, que é um efeito reativo global que pode ser destruído manualmente. Um EffectRef
tem uma operação de destruição.
destroy()
: Encerra o efeito, removendo-o de quaisquer execuções programadas futuras.
computed
E se houver outro valor que dependa dos valores de outros signals e precise ser recalculado sempre que alguma dessas dependências mudar?
Nesse caso, podemos usar a função computed()
para criar um novo signal que se atualiza automaticamente sempre que suas dependências mudam.
O computed()
cria um signal de memorização, que calcula seu valor a partir dos valores de um número qualquer de signals de entrada.
Como usar
totalDeJogos = computed(() => jogos().length)
A função computed
retorna outro Signal, todos os signals usados por computation serão rastreados como dependências, e o valor do novo signal recalculado sempre que alguma dessas dependências mudar.
Note que a função computed retorna um Signal e não um WritableSignal, o que significa que ele não pode ser modificado manualmente usando métodos como
set
,update
. Em vez disso, ele é atualizado automaticamente sempre que um de seus signals pais dependentes muda.
Agora podemos adicionar um effect ou criar outro signal com a função computed baseado neste novo Signal.
Qualquer mudança nos valores agora será propagada no gráfico de dependências.
Para ilustrar os conceitos aprendidos, criei esse projeto que explica os Angular signals e demonstra como criar e atualizar signals e criar valores computados.
É isso! Neste artigo, mergulhamos no mundo fascinante dos Angular Signals, explorando sua natureza, necessidade e aplicabilidade em projetos reais. Espero que esta jornada tenha não apenas esclarecido o conceito e utilidade dos Signals, mas também despertado a sua curiosidade e entusiasmo para integrá-los em suas próprias aplicações Angular.
Para levar seu aprendizado um passo adiante, tenho uma sugestão especial: que tal colocar em prática tudo o que aprendeu aqui e muito mais? Na formação Desenvolva Aplicações Escaláveis com Angular aqui na Alura, construímos juntos uma aplicação completa do zero, onde você terá a oportunidade de aplicar conceitos de Angular Signals e explorar o potencial do Material Design para criar interfaces atraentes e funcionais.
Essa formação é a oportunidade perfeita para aprofundar seus conhecimentos, experimentar com as ferramentas mais atuais e dar vida às suas ideias. Junte-se a nós nessa jornada de aprendizado e inovação, e vamos juntos criar aplicações incríveis com Angular e Material Design!