{ Angular Signals } Como deixar seu código mais reativo

Angular

{ Angular Signals } Como deixar seu código mais reativo

Gabriel Nascimento
Escrito por Gabriel Nascimento em 10 de maio de 2023

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 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:

Esquema visual do Angular Signals notificando os consumidores

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:

Esquema visual de interface reativa

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 tipo CreateSignalOptions que inclui um método equal 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?

Ilustração de controle e TV

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?

Ilustração de farol

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:

Fluxo completo de notificação do Angular Signals

Diferença entre Change Detection e Angular Signals

Ilustração de 2 bonecos lutando

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:

Hey,

o que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

One Reply to “{ Angular Signals } Como deixar seu código mais reativo”

Gabriel Nascimento