Angular Signals

Angular Signals

Hoje vamos falar um pouco sobre signals, uma nova funcionalidade introduzida pelo Angular em suas atualizações mais recentes. Essa inovação nos permite implementar funcionalidades de maneira mais eficiente, oferecendo melhor desempenho. Vamos analisar como essa característica opera na prática.

O Que é signals?

Basicamente o signals é essencialmente um "wrapper" em torno de um valor que emite uma notificação sempre que esse valor sofre alterações. Ele pode conter tanto valores primitivos quanto estruturas mais complexas, como objetos.

Para acessar o valor contido no Signal, utilizamos uma função getter, permitindo que o Angular rastreie onde o Signal é utilizado em nosso código. Além disso, os Signals podem ser de leitura ou escrita.

Agora, vamos examinar alguns exemplos práticos. Para começar, vamos criar um Signal simples:

import { CommonModule } from '@angular/common';
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  protected primeiroExemplo = signal("Hello world")
}
<p>{{ primeiroExemplo() }}</p>

Esse é o resultado exibido no navegador. Note que estamos fazendo uma chamada de função em nosso template, o que não é a abordagem correta. No entanto, não se preocupe, vamos entender por que isso ocorre e como podemos corrigir isso utilizando o Signal.

Agora, vamos explorar como funciona a manipulação de dados com o Signal.

SET

export class AppComponent {
  protected counter = signal(0)
  incrementar() {
    this.counter.set(1)
  }
}
<p>{{ counter() }}</p>
<button (click)="incrementar()">Somar</button>

Neste exemplo, nosso valor começa com o número 0 e, ao clicar no botão, obteremos a seguinte saída no navegador:

O valor agora é atualizado para "1", pois estamos utilizando o método "set" para modificar o valor do sinal e definir um novo valor.

Para uma abordagem mais eficaz para esse exemplo, podemos usar a função "update".

UPDATE

export class AppComponent {
  protected counter = signal(0)
  incrementar() {
    this.counter.update(atual => atual +1)
  }
}

Dessa forma, ao clicar no botão "Somar", nosso valor será atualizado, incrementando sempre mais um em relação ao valor atual.

COMPUTED

Também é possível utilizar o computed. Essa funcionalidade nos permite usar um signal como referência para atualizar um valor específico, com base nas mudanças de valor de outro signal.

export class AppComponent {
  protected exemploCount = signal(0);
  protected exemploComputed = computed(() => `${this.exemploCount()} computed!`)

  atualizarValor() {
    this.exemploCount.set(1)
  }
}
<p>{{ exemploCount() }}</p>
<p>{{ exemploComputed() }}</p>
<button (click)="atualizarValor()">Atualizar</button>

Dessa forma, ao clicar em "atualizar", mudaremos o valor do sinal "exemploCount". Em seguida, o "exemploComputed" será notificado de que houve uma alteração no sinal "exemploCount" e, consequentemente, atualizará seu valor.

Essa funcionalidade abre muitas oportunidades para futuras implementações no Angular. Agora, vamos apresentar um exemplo e explicar por que não era recomendado utilizar chamadas de função diretamente em nosso template, e porque com o signal isso não representa problemas.

export class AppComponent {

  protected counter = signal(0)

  incrementar() {
    console.log('incrementar')
    this.counter.update(atual => atual + 1)
  }

  obterValor() {
    console.log('obterValor')
    return 10;
  }
}
<p>{{ counter() }}</p>
<p>{{ obterValor() }}</p>

<button (click)="incrementar()">Somar</button>

Ao criarmos este exemplo, vamos simular o clássico problema relacionado à chamada de uma função no template.

É importante notar que, ao simplesmente atualizarmos a página, a função "obterValor" foi acionada três vezes desnecessariamente, enquanto nosso sinal ainda não foi ativado. Esse comportamento desnecessário pode impactar negativamente o desempenho da nossa aplicação, especialmente em cenários onde a função executada é complexa ou envolve operações custosas em termos de processamento.

Observe que, ao acionar a função "incrementar" para atualizar o valor do nosso sinal, a função "obterValor" também é executada. Isso ocorre porque o Angular não consegue determinar quando o valor daquela função foi atualizado. Portanto, a cada mudança que ocorre no componente, a função chamada no template é executada novamente. Esse comportamento ressalta a importância de evitar chamadas de funções diretas no template, pois isso pode resultar em execuções desnecessárias e impactar negativamente o desempenho da aplicação.

Se você precisar de uma funcionalidade semelhante, considere utilizar o "Computed", que demonstramos no exemplo anterior. O "Computed" será acionado somente quando necessário, pois o Angular consegue identificar mudanças em valores específicos com o sinal, resultando em melhor desempenho.

Chegamos ao fim deste post e espero que ele tenha despertado sua curiosidade sobre o "signal". Acredito que vale a pena uma leitura mais aprofundada; por isso, sugiro que você acesse a documentação e coloque a mão na massa. Faça seus testes e tire suas próprias conclusões. A exploração é a melhor maneira de aprender!