Entendendo os Signals do Angular

Entendendo os Signals do Angular
Vinicios Neves
Vinicios Neves

Compartilhe

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!

https://media.tenor.com/mOuExrel7lQAAAAC/magic-dark.gif

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.

Diagrama de hierarquia em fundo azul escuro com elementos em azul claro e texto em branco. No topo, há uma ilustração de um avatar com fones e sinal de Wi-Fi, ao lado do texto 'Zone js'. Abaixo, um retângulo maior rotulado 'Raiz' está conectado a três componentes. 'Componente 1' se ramifica para 'Comp.1-1' e 'Comp.1-2'. 'Componente 2' se ramifica para 'Comp.2-1', 'Comp.2-2' e 'Comp.2-3'. 'Componente 3' se divide em 'Comp.3-1', que por sua vez se ramifica em 'Comp.3-1-1', 'Comp.3-1-2' e 'Comp.3-1-3'. No canto inferior direito, a palavra 'alura' aparece em branco.

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:

Diagrama de hierarquia com design de interface futurista em fundo azul escuro e elementos em azul claro. No canto superior esquerdo, uma ilustração de um avatar com fones e sinal de Wi-Fi ao lado do texto 'Zone js'. Ao lado do avatar, uma caixa de diálogo com o texto 'Ei! Algo aconteceu, rode Detecção de Mudança'. Abaixo, um retângulo maior intitulado 'Raiz' está no topo, ligado a três componentes. 'Componente 1' está ligado a 'Comp.1-1' e 'Comp.1-2'. 'Componente 2' leva a 'Comp.2-1', 'Comp.2-2' e 'Comp.2-3'. 'Componente 3' conduz a 'Comp.3-1', que por sua vez se divide em 'Comp.3-1-1', 'Comp.3-1-2' e 'Comp.3-1-3'. À esquerda, uma linha leva de 'Componente 1' a uma caixa rotulada 'Evento'. No canto inferior direito, a palavra 'alura' em branco.
  • 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.
Diagrama de hierarquia interativa em fundo azul escuro com elementos em rosa e ícones azuis. No topo esquerdo, há uma ilustração de um avatar com fones e sinal de Wi-Fi junto ao texto 'Zone js'. Uma caixa de diálogo ao lado do avatar diz 'Ei! Algo aconteceu, rode Detecção de Mudança'. O elemento central 'Raiz' está conectado a três caixas: 'Componente 1', 'Componente 2' e 'Componente 3', todas com a sigla 'DM*' e um ícone de engrenagem azul indicando 'Detecção de mudança'. 'Componente 1' leva a 'Comp.1-1' e 'Comp.1-2'; 'Componente 2' a 'Comp.2-1', 'Comp.2-2' e 'Comp.2-3'; e 'Componente 3' a 'Comp.3-1', que se subdivide em 'Comp.3-1-1', 'Comp.3-1-2' e 'Comp.3-1-3', todos marcados com 'DM*'. À esquerda, uma linha conecta 'Componente 1' a uma caixa rotulada 'Evento'. No canto inferior direito está o logotipo 'alura'. No canto inferior esquerdo, uma legenda explica 'DM* = Detecção de mudança' com o ícone de engrenagem correspondente.

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étodo equal para comparar dois valores do tipo T. Se o objeto options não for fornecido, a função defaultEquals será usada. Esta função compara dois valores do mesmo tipo T, usando uma combinação dos operadores === e Object.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.

Banner promocional da Semana Carreira Tech, organizada pela Alura e FIAP. Texto: 'Descubra como graduações tech estão transformando o futuro. Cinco lives gratuitas para você mergulhar nas áreas mais transformadoras da atualidade, desde o que se estuda nas graduações até a prática do mercado. Garanta sua vaga de 01 a 05 de julho.' Imagem de profissionais usando equipamentos tecnológicos avançados, como óculos de realidade aumentada. Botão destacado com a chamada 'Garanta sua vaga'. Logotipos da Alura e FIAP no canto superior direito.

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.

Diagrama explicativo sobre o fluxo de notificações de mudança em fundo azul escuro. No topo, um bloco azul claro rotulado 'Signal' está ligado a três blocos mais abaixo, intitulados 'Consumidor1', 'Consumidor2' e 'Consumidor3'. Ao lado do 'Signal', uma anotação diz 'Altera valor por: - set - update'. Abaixo dos consumidores, uma frase em branco afirma: 'Quando uma mudança acontece por (set/update), um signal notifica seus consumidores'. Uma linha tracejada separa esta seção de outra abaixo, que mostra outro bloco 'Signal' com a anotação 'Valor mudou', ligado a 'Consumidor1', 'Consumidor2' e 'Consumidor3', com setas rotuladas 'notifica'. No canto inferior direito está o logotipo 'alura' em branco.

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.

Diagrama de fluxo de dados e notificações em fundo degradê azul. No centro superior, um retângulo azul claro com o texto 'Signal' e ao lado, uma anotação 'Altera valor por: - set - update' seguido por 'Novo VALOR'. O 'Signal' emite três notificações: à esquerda para 'effectFn' que leva a 'Side effect', ao centro para 'Computation' que leva a 'Novo Signal', e à direita para outro 'effectFn' seguido por 'Side effect'. Cada 'Novo Signal' e 'Computation' está conectado um ao outro indicando um ciclo computacional. As conexões são marcadas com as palavras 'notifica' e 'computed'. No canto inferior direito, o logotipo 'alura' em branco.

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!

Vinicios Neves
Vinicios Neves

Vinicios é engenheiro de software, envolvido na arquitetura, design e implementação de microsserviços, micro frontends e sistemas distribuídos. Tem experiência significativas em aplicativos, integração e arquitetura corporativa. É Engenheiro de Software pela UNESA e Arquiteto de Software pela PUC Minas.

Veja outros artigos sobre Front-end