NgRx Signal Store vs NgRx Clássico em 2026: Qual Escolher?

Comparação detalhada entre NgRx Signal Store e NgRx clássico para gerenciamento de estado Angular. Aprenda quando usar cada abordagem com exemplos de código práticos.

NgRx Signal Store vs NgRx Clássico em 2026: Qual Escolher?

NgRx Signal Store representa uma mudança fundamental no gerenciamento de estado do Angular, substituindo o padrão tradicional inspirado no Redux (actions-reducers-selectors) por uma abordagem reativa baseada em signals. Com NgRx 22 (publicado em agosto de 2026), Signal Store ganha extensões de resource, signals profundos dinâmicos e melhor composabilidade. Esta comparação examina ambas as soluções para ajudar desenvolvedores Angular a tomar decisões arquiteturais informadas.

Guia de Decisão Rápida

Usar Signal Store para estado em nível de funcionalidade, aplicações de complexidade média e integração nativa com a API Resource do Angular. Usar NgRx Clássico para aplicações empresariais que requerem trilhas de auditoria rigorosas, efeitos colaterais complexos e depuração extensiva com DevTools.

Entendendo as Diferenças Arquiteturais

NgRx clássico segue o padrão Redux: as actions descrevem eventos, os reducers produzem novo estado imutável, e os selectors extraem dados. Cada mudança de estado flui através de um pipeline previsível e rastreável.

Signal Store adota uma abordagem diferente. Em vez de despachar actions através de reducers, o estado vive em signals reativos. As atualizações acontecem através de métodos que chamam patchState(), e os valores derivados vêm de signals computados em vez de selectors memorizados.

AspectoNgRx ClássicoSignal Store
Modelo MentalRedux/FluxServiços Reativos
BoilerplateAlto (actions, reducers, selectors, effects)Baixo (state, methods, computed)
ReatividadeObservables RxJSSignals Angular
API ResourceIntegração manualNativa com @ngrx/signals/resource
DevToolsRedux DevTools completoSignal Store DevTools (via plugin)
Curva de aprendizadoÍngremeModerada
Tamanho do BundleMaior (~15-20KB gzipped)Menor (~4KB gzipped)

Para desenvolvedores já familiarizados com Signals do Angular, Signal Store parece uma extensão natural das primitivas reativas do framework.

NgRx Clássico: O Padrão Redux no Angular

NgRx clássico organiza o gerenciamento de estado em torno de actions, reducers, selectors e effects. Essa separação impõe previsibilidade ao custo de verbosidade.

books.actions.tstypescript
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Book } from './book.model';

export const BooksActions = createActionGroup({
  source: 'Books',
  events: {
    'Load Books': emptyProps(),
    'Load Books Success': props<{ books: Book[] }>(),
    'Load Books Failure': props<{ error: string }>(),
    'Add Book': props<{ book: Book }>(),
    'Remove Book': props<{ id: string }>(),
  },
});

A API createActionGroup agrupa actions relacionadas sob uma única fonte, melhorando a legibilidade no DevTools e reduzindo o boilerplate comparado com chamadas individuais de createAction.

books.reducer.tstypescript
import { createReducer, on } from '@ngrx/store';
import { BooksActions } from './books.actions';
import { Book } from './book.model';

export interface BooksState {
  books: Book[];
  loading: boolean;
  error: string | null;
}

const initialState: BooksState = {
  books: [],
  loading: false,
  error: null,
};

export const booksReducer = createReducer(
  initialState,
  on(BooksActions.loadBooks, (state) => ({
    ...state,
    loading: true,
    error: null,
  })),
  on(BooksActions.loadBooksSuccess, (state, { books }) => ({
    ...state,
    books,
    loading: false,
  })),
  on(BooksActions.loadBooksFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error,
  }))
);

Os reducers continuam sendo funções puras que retornam novos objetos de estado. A função on() lida com a correspondência de actions e atualizações de estado de forma concisa.

books.selectors.tstypescript
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { BooksState } from './books.reducer';

export const selectBooksState = createFeatureSelector<BooksState>('books');

export const selectAllBooks = createSelector(
  selectBooksState,
  (state) => state.books
);

export const selectBooksLoading = createSelector(
  selectBooksState,
  (state) => state.loading
);

export const selectPublishedBooks = createSelector(
  selectAllBooks,
  (books) => books.filter((book) => book.published)
);

Os selectors fornecem memorização automaticamente. O selector selectPublishedBooks só recalcula quando o array de livros muda, tornando as leituras eficientes mesmo em stores atualizados frequentemente.

NgRx Signal Store: Gerenciamento de Estado Signals-First

Signal Store consolida estado, valores computados e métodos em um único serviço injetável. A documentação oficial do NgRx o descreve como uma "solução de gerenciamento de estado completamente equipada com suporte nativo para Signals do Angular."

books.store.tstypescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
import { BooksService } from './books.service';
import { Book } from './book.model';

interface BooksState {
  books: Book[];
  loading: boolean;
  error: string | null;
}

const initialState: BooksState = {
  books: [],
  loading: false,
  error: null,
};

export const BooksStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withComputed(({ books }) => ({
    publishedBooks: computed(() => books().filter((book) => book.published)),
    totalBooks: computed(() => books().length),
  })),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      patchState(store, { loading: true, error: null });
      try {
        const books = await booksService.getAll();
        patchState(store, { books, loading: false });
      } catch (error) {
        patchState(store, { loading: false, error: 'Failed to load books' });
      }
    },
    addBook(book: Book) {
      patchState(store, ({ books }) => ({ books: [...books, book] }));
    },
    removeBook(id: string) {
      patchState(store, ({ books }) => ({
        books: books.filter((book) => book.id !== id),
      }));
    },
  }))
);

Todo o gerenciamento de estado de uma funcionalidade cabe em um único arquivo. As propriedades de estado se tornam signals automaticamente, signals computados substituem selectors, e métodos substituem despachos de actions.

NgRx 22: Extensões de Resource e withProps

NgRx 22 introduz o ponto de entrada @ngrx/signals/resource, habilitando integração direta com a API Resource do Angular. A funcionalidade withProps centraliza a injeção de serviços, enquanto as extensões de resource lidam com padrões assíncronos comuns.

books-resource.store.tstypescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withProps } from '@ngrx/signals';
import { extendResource, withPreviousValueOnLoading, withValueOnError } from '@ngrx/signals/resource';
import { httpResource } from '@angular/core';
import { BooksService } from './books.service';
import { Book } from './book.model';

export const BooksResourceStore = signalStore(
  { providedIn: 'root' },
  // Injeção de serviços via withProps (privado por convenção com underscore)
  withProps(() => ({
    _booksService: inject(BooksService),
  })),
  // Configuração de resources com extensões
  withProps((store) => ({
    _booksResource: extendResource(
      httpResource<Book[]>({
        url: '/api/books',
        defaultValue: [],
      }),
      withPreviousValueOnLoading(),  // Mantém o último valor durante o recarregamento
      withValueOnError([])           // Retorna array vazio em caso de erro
    ),
  })),
  // Exposição somente leitura para os consumidores
  withProps((store) => ({
    booksResource: store._booksResource.asReadonly(),
  })),
  withComputed((store) => ({
    books: computed(() => store.booksResource.value()),
    isLoading: computed(() => store.booksResource.isLoading()),
    publishedBooks: computed(() => 
      store.booksResource.value().filter((book) => book.published)
    ),
  }))
);

As extensões de resource resolvem dois problemas comuns: a cintilação durante recarregamentos (corrigida por withPreviousValueOnLoading) e o tratamento de estados de erro (corrigido por withValueOnError ou withPreviousValueOnError). Essas extensões se compõem e podem ser fornecidas globalmente via provideResourceExtensions.

Gerenciamento de Entidades com withEntities

Ambas as abordagens suportam coleções de entidades, mas a funcionalidade withEntities do Signal Store reduz drasticamente o código.

books-entity.store.tstypescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods } from '@ngrx/signals';
import {
  addEntity,
  removeEntity,
  setAllEntities,
  updateEntity,
  withEntities,
} from '@ngrx/signals/entities';
import { BooksService } from './books.service';
import { Book } from './book.model';

export const BooksEntityStore = signalStore(
  { providedIn: 'root' },
  withEntities<Book>(),
  withComputed(({ entities }) => ({
    publishedBooks: computed(() => entities().filter((book) => book.published)),
  })),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      const books = await booksService.getAll();
      patchState(store, setAllEntities(books));
    },
    addBook(book: Book) {
      patchState(store, addEntity(book));
    },
    updateBook(id: string, changes: Partial<Book>) {
      patchState(store, updateEntity({ id, changes }));
    },
    removeBook(id: string) {
      patchState(store, removeEntity(id));
    },
  }))
);

A funcionalidade withEntities fornece automaticamente os signals ids, entityMap e entities. Os atualizadores de entidades como addEntity, removeEntity e updateEntity lidam com mutações de coleções.

Pronto para mandar bem nas entrevistas de Angular?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Features de Store Personalizadas para Reusabilidade

A função signalStoreFeature do Signal Store permite extrair e reutilizar preocupações transversais entre stores. NgRx 22 adiciona SignalStoreFeatureType para simplificar a extração de tipos.

with-request-status.tstypescript
import { computed } from '@angular/core';
import { signalStoreFeature, withComputed, withState, SignalStoreFeatureType } from '@ngrx/signals';

type RequestStatus = 'idle' | 'pending' | 'fulfilled' | 'error';

export function withRequestStatus() {
  return signalStoreFeature(
    withState<{ requestStatus: RequestStatus }>({ requestStatus: 'idle' }),
    withComputed(({ requestStatus }) => ({
      isPending: computed(() => requestStatus() === 'pending'),
      isFulfilled: computed(() => requestStatus() === 'fulfilled'),
      isError: computed(() => requestStatus() === 'error'),
    }))
  );
}

// NgRx 22: extração de tipos sem redeclaração manual
export type RequestStatusFeature = SignalStoreFeatureType<typeof withRequestStatus>;

export function setPending(): { requestStatus: RequestStatus } {
  return { requestStatus: 'pending' };
}

export function setFulfilled(): { requestStatus: RequestStatus } {
  return { requestStatus: 'fulfilled' };
}

export function setError(): { requestStatus: RequestStatus } {
  return { requestStatus: 'error' };
}

Esta feature personalizada agora pode ser composta em qualquer store que precise de rastreamento do estado de carregamento.

books-with-status.store.tstypescript
import { inject } from '@angular/core';
import { patchState, signalStore, withMethods } from '@ngrx/signals';
import { setAllEntities, withEntities } from '@ngrx/signals/entities';
import { withRequestStatus, setFulfilled, setPending, setError } from './with-request-status';
import { BooksService } from './books.service';
import { Book } from './book.model';

export const BooksStore = signalStore(
  { providedIn: 'root' },
  withEntities<Book>(),
  withRequestStatus(),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      patchState(store, setPending());
      try {
        const books = await booksService.getAll();
        patchState(store, setAllEntities(books), setFulfilled());
      } catch {
        patchState(store, setError());
      }
    },
  }))
);

O store composto agora expõe os signals computados isPending(), isFulfilled() e isError() sem duplicar lógica.

Rastreamento Síncrono do Estado com watchState

NgRx 22 adiciona a função watchState para rastreamento síncrono de mudanças de estado, útil para funcionalidades de undo/redo ou histórico de estado.

state-history.tstypescript
import { inject, DestroyRef } from '@angular/core';
import { watchState } from '@ngrx/signals';
import { BooksStore } from './books.store';

export function setupStateHistory() {
  const store = inject(BooksStore);
  const destroyRef = inject(DestroyRef);
  const history: unknown[] = [];

  // Rastreamento síncrono: captura cada mudança de estado, sem coalescência
  watchState(store, (state) => {
    history.push(structuredClone(state));
  });

  return { history };
}

Diferente do rastreamento baseado em effects que coalesce atualizações, watchState dispara sincronamente em cada chamada patchState, capturando o histórico completo do estado.

Padrões de Integração em Componentes

Signal Store se integra naturalmente com a sintaxe de templates do Angular através de leituras de signals.

books-list.component.tstypescript
import { Component, inject } from '@angular/core';
import { BooksStore } from './books.store';

@Component({
  selector: 'app-books-list',
  standalone: true,
  template: `
    @if (store.isPending()) {
      <div class="loading-spinner">Carregando livros...</div>
    }

    @if (store.isError()) {
      <div class="error-message">Falha ao carregar livros</div>
    }

    @for (book of store.entities(); track book.id) {
      <div class="book-card">
        <h3>{{ book.title }}</h3>
        <p>{{ book.author }}</p>
        <button (click)="store.removeBook(book.id)">Remover</button>
      </div>
    }

    <p>Total: {{ store.totalBooks() }} livros</p>
  `,
})
export class BooksListComponent {
  readonly store = inject(BooksStore);

  constructor() {
    this.store.loadBooks();
  }
}

Sem gerenciamento de subscriptions. Sem pipes async. Os signals se integram com a detecção de mudanças zoneless do Angular para performance otimizada. Com OnPush como estratégia de detecção de mudanças padrão no Angular 22, os componentes Signal Store se beneficiam de atualizações precisas e granulares.

Considerações de Performance e Tamanho de Bundle

A arquitetura mais leve do Signal Store se traduz em bundles menores. O pacote core @ngrx/signals adiciona aproximadamente 4KB comprimido com gzip, comparado com 15-20KB para o stack completo do NgRx clássico (@ngrx/store, @ngrx/effects, @ngrx/entity).

Signal Store também se beneficia da detecção de mudanças baseada em signals do Angular. Signals computados só recalculam quando as dependências mudam, e componentes só re-renderizam quando signals consumidos são atualizados. Esta reatividade granular elimina ciclos desnecessários de detecção de mudanças comuns em padrões baseados em Observables.

Para aplicações que já usam RxJS extensivamente, os selectors baseados em Observables do NgRx clássico se integram perfeitamente com pipelines existentes.

Requisitos do NgRx 22

NgRx 22 requer Angular 22.x, TypeScript 6.0.x e RxJS 6.5.x ou 7.5.x. Atualizar com ng update @ngrx/signals@22 para projetos Signal Store.

Estratégia de Migração de Clássico para Signal Store

Migrar uma aplicação NgRx clássica existente não requer uma reescrita completa. Ambas as soluções podem coexistir, permitindo adoção incremental.

  • Começar com novas funcionalidades usando Signal Store
  • Migrar módulos de funcionalidades isolados um de cada vez
  • Manter funcionalidades complexas com muitos effects no NgRx clássico se o custo de migração exceder o benefício
  • Usar os utilitários de interoperabilidade do NgRx Signals para conectar estado baseado em Observables e baseado em signals

NgRx Store clássico e Signal Store podem rodar lado a lado na mesma aplicação. O time do NgRx suporta oficialmente ambas as abordagens e fornece utilitários de interoperabilidade para conectá-las.

Quando Escolher Cada Abordagem

Escolher Signal Store quando:

  • Se está construindo novas aplicações Angular 22+
  • Se está gerenciando estado em nível de funcionalidade ou componente
  • Se está integrando com a API Resource do Angular e Signal Forms
  • Experiência do desenvolvedor e redução de boilerplate são prioridade
  • O time tem experiência limitada com NgRx
  • O tamanho do bundle importa

Escolher NgRx Clássico quando:

  • Se está mantendo aplicações NgRx existentes
  • Se requer trilhas de auditoria rigorosas com logging de actions
  • Se está construindo fluxos de trabalho complexos com efeitos colaterais extensivos
  • Se precisa de depuração time-travel completa do Redux DevTools
  • O time tem experiência profunda em NgRx

Para os fundamentos de gerenciamento de estado, entender ambas as abordagens prepara desenvolvedores para diversos codebases Angular.

Sources

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Signal Store vs NgRx Clássico: Pontos Chave

  • Signal Store reduz o boilerplate de gerenciamento de estado entre 60-70% comparado com NgRx clássico
  • NgRx 22 adiciona extensões de resource (withPreviousValueOnLoading, withValueOnError) para tratamento transparente de estado assíncrono
  • A funcionalidade withProps centraliza a injeção de serviços, eliminando listas de parâmetros repetidas entre features
  • watchState habilita rastreamento síncrono de estado para funcionalidades de undo/redo e histórico de estado
  • NgRx clássico continua sendo a melhor escolha para aplicações empresariais que requerem trilhas de auditoria rigorosas e efeitos colaterais complexos
  • Ambas as abordagens coexistem na mesma aplicação, permitindo migração incremental
  • Para novos projetos Angular 22+, Signal Store deve ser a escolha padrão a menos que requisitos específicos demandem NgRx clássico
Desafio do dia

Você saberia encontrar o bug em Angular?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 9 de outubro de 2026

Compartilhar

Artigos relacionados