Publicado em

- 6 minutos de leitura

Gerenciamento de Estado com Angular Signals

img of Gerenciamento de Estado com Angular Signals

Gerenciar o estado de uma aplicação é uma das tarefas mais críticas no desenvolvimento front-end. O estado pode ser definido como os dados que a sua aplicação precisa armazenar e manipular ao longo do tempo. Isso inclui informações do usuário, dados vindos de uma API, o estado da interface (como temas ou modais abertas) e muito mais. Em aplicações pequenas, o gerenciamento de estado pode ser simples, utilizando apenas a comunicação entre componentes via input() e output().

No entanto, à medida que a aplicação cresce em complexidade, compartilhar estado entre componentes que não têm uma relação direta de pai e filho se torna um desafio. É nesse ponto que estratégias de gerenciamento de estado mais estruturadas se tornam essenciais. O Angular oferece flexibilidade para lidar com essa complexidade, permitindo desde a criação de soluções simples com Services e Signals até a adoção de bibliotecas mais robustas e opinativas, como NgRx e NGXS.

O que é Gerenciamento de Estado?

Gerenciamento de Estado (State Management) é a prática de centralizar e controlar a “fonte da verdade” dos dados de uma aplicação. Em vez de cada componente manter seu próprio estado local de forma isolada, o estado compartilhado é armazenado em um local central, geralmente um serviço.

Isso torna o fluxo de dados na aplicação mais previsível e fácil de depurar. Quando um estado precisa ser modificado, a alteração é feita nesse local central, e todos os componentes que dependem desse estado são notificados e atualizados automaticamente. Essa abordagem evita inconsistências e simplifica a comunicação entre partes distantes da sua aplicação.

Quando Usar?

Embora seja uma ferramenta poderosa, nem toda aplicação precisa de uma solução complexa de gerenciamento de estado desde o início. Para comunicação simples entre componentes pai e filho, as funções input() e output() são perfeitamente adequadas.

A necessidade de uma estratégia de gerenciamento de estado mais elaborada surge quando:

  • Múltiplos componentes que não são diretamente relacionados precisam acessar e manipular os mesmos dados.
  • O estado precisa persistir durante a navegação entre diferentes rotas.
  • A lógica de negócio para alterar o estado é complexa e precisa ser reutilizada em vários locais.
  • Você precisa de um fluxo de dados unidirecional e previsível para facilitar a depuração e a manutenção.

Cenários onde pode ser aplicado

O gerenciamento de estado é útil em diversos cenários comuns em aplicações web modernas:

  • Autenticação de Usuário: Manter o estado de login, informações do perfil do usuário e suas permissões acessíveis em toda a aplicação.
  • Carrinho de Compras: Gerenciar os itens, a quantidade e o valor total de um carrinho de compras, que pode ser atualizado a partir de várias páginas e componentes.
  • Estado da Interface (UI State): Controlar elementos globais da interface, como a visibilidade de um menu lateral, o tema (claro ou escuro) da aplicação ou o idioma selecionado.
  • Dados de API Compartilhados: Armazenar em cache dados buscados de uma API para que diferentes componentes possam exibi-los sem a necessidade de múltiplas requisições.

Como criar apenas com Angular Services e Signals

Com a introdução dos Signals, o Angular oferece uma maneira nativa, reativa e muito eficiente de gerenciar o estado. Combinando Services com Signals, podemos criar uma solução de gerenciamento de estado leve e poderosa sem a necessidade de bibliotecas externas.

Nesta abordagem, o serviço detém o estado (os signals), expõe uma versão somente leitura para os componentes e fornece métodos públicos para modificar o estado.

counter.service.ts

   import { Injectable, signal, computed } from '@angular/core'

@Injectable({
	providedIn: 'root'
})
export class CounterService {
	// 1. O estado é privado para evitar modificações externas diretas.
	private count = signal(0)

	// 2. Expomos o estado através de um computed para garantir que seja somente leitura.
	public readonly count = computed(() => this.count())

	// 3. Criamos um estado derivado (computed signal).
	public readonly isEven = computed(() => this.count() % 2 === 0)

	// 4. Métodos públicos para modificar o estado de forma controlada.
	public increment(): void {
		this.count.update((current) => current + 1)
	}

	public decrement(): void {
		this.count.update((current) => current - 1)
	}
}

counter.component.ts

   import { Component, inject, ChangeDetectionStrategy } from '@angular/core'
import { CounterService } from './counter.service'

@Component({
	selector: 'app-counter',
	standalone: true,
	templateUrl: './counter.component.html',
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
	private counterService = inject(CounterService)

	// O componente apenas lê os signals do serviço.
	public count = this.counterService.count
	public isEven = this.counterService.isEven

	// As ações do componente chamam os métodos do serviço.
	public increment(): void {
		this.counterService.increment()
	}

	public decrement(): void {
		this.counterService.decrement()
	}
}

counter.component.html

   <div>
	<h2>Contador com Signals</h2>
	<p>Valor atual: {{ count() }}</p>

	@if (isEven()) {
	<p>O número é par.</p>
	} @else {
	<p>O número é ímpar.</p>
	}

	<button (click)="increment()">Incrementar</button>
	<button (click)="decrement()">Decrementar</button>
</div>

Imagem com o texto: Curso Angular Moderno e um homem de pele parda usando óculos com a logo do Angular atrás. Logo abaixo existe um botão com o texto "Eu quero!"

Como criar usando Services Angular e RxJS (solução mais antiga)

Antes dos Signals, a abordagem comum para gerenciar estado reativo em Angular era usar Services com BehaviorSubject do RxJS. Um BehaviorSubject armazena o valor mais recente e o emite para novos inscritos, sendo ideal para representar o estado.

O serviço mantém um BehaviorSubject privado e expõe sua versão Observable para os componentes.

counter-rxjs.service.ts

   import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'

@Injectable({
	providedIn: 'root'
})
export class CounterRxjsService {
	private count = new BehaviorSubject(0)

	// Expõe um Observable para que os componentes não possam emitir novos valores.
	public readonly count = this.count.asObservable()

	public increment(): void {
		this.count.next(this.count.value + 1)
	}

	public decrement(): void {
		this.count.next(this.count.value - 1)
	}
}

counter-rxjs.component.ts

   import { Component, inject, ChangeDetectionStrategy } from '@angular/core'
import { CommonModule } from '@angular/common'
import { CounterRxjsService } from './counter-rxjs.service'

@Component({
	selector: 'app-counter-rxjs',
	standalone: true,
	imports: [CommonModule], // Necessário para o pipe 'async'
	templateUrl: './counter-rxjs.component.html',
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterRxjsComponent {
	private counterService = inject(CounterRxjsService)

	public count = this.counterService.count

	public increment(): void {
		this.counterService.increment()
	}

	public decrement(): void {
		this.counterService.decrement()
	}
}

counter-rxjs.component.html

   <div>
	<h2>Contador com RxJS</h2>

	@if (count | async; as value) {
	<p>Valor atual: {{ value }}</p>
	}

	<button (click)="increment()">Incrementar</button>
	<button (click)="decrement()">Decrementar</button>
</div>

Bibliotecas que podem ser usadas para soluções mais robustas

Para aplicações de grande escala, com fluxos de estado muito complexos e interconectados, o uso de bibliotecas dedicadas pode trazer mais estrutura e previsibilidade. As mais populares no ecossistema Angular incluem NgRx, NGXS, Elf e Akita (que não é mais mantida ativamente).

NgRx e NGXS são inspiradas na arquitetura Redux e introduzem conceitos como Store, Actions, Reducers e Selectors. Elas impõem um fluxo de dados unidirecional estrito, tornando o estado da aplicação extremamente previsível.

Para desenvolvedores que preferem uma abordagem que não seja baseada em Redux, Elf e Akita oferecem uma alternativa, focando em um modelo mais orientado a objetos com stores que se assemelham a serviços, o que pode ser mais intuitivo para alguns.

Conclusão

O gerenciamento de estado é um aspecto fundamental do desenvolvimento de aplicações Angular, e a plataforma oferece um espectro de soluções para diferentes níveis de complexidade. A abordagem moderna com Services e Signals é a recomendação para a maioria dos casos de uso, pois combina simplicidade, reatividade e excelente desempenho com as ferramentas nativas do framework. A solução com RxJS, embora mais antiga, ainda é robusta e amplamente utilizada em muitos projetos existentes.

A escolha da ferramenta certa depende das necessidades do seu projeto. Comece com a solução mais simples e nativa que o Angular oferece. Se a complexidade da sua aplicação crescer a ponto de justificar uma arquitetura mais rígida e com mais ferramentas de depuração, considere explorar bibliotecas dedicadas. O importante é manter seu fluxo de dados organizado, previsível e fácil de manter.

Assine nossa Newsletter

Receba novos posts como esse na sua caixa de e-mail!

Sobre o autor

Author's photo
Henrique Custódia Arquiteto Frontend, entusiasta Angular, cat lover, criador de conteúdo e fundador da Code Dimension!