NgRx Signal Store vs 従来型NgRx:2026年の選択ガイド

NgRx Signal Storeと従来のNgRxを比較し、2026年のAngularプロジェクトに最適な状態管理ソリューションの選択方法を解説します。

NgRx Signal Store vs 従来型NgRx:2026年の選択ガイド

NgRx Signal Storeは、Angular状態管理における根本的なパラダイムシフトを象徴しています。従来のReduxスタイルのアクション・リデューサー・セレクターパターンから、シグナルベースのリアクティブアプローチへと移行し、開発者体験を大きく変えました。本記事では、2026年におけるAngular開発者が適切なアーキテクチャを選択できるよう、両ソリューションを詳細に比較します。

クイック判断ガイド

Signal Storeは、機能レベルの状態管理や中規模の複雑さを持つアプリケーションで、ボイラープレートの削減を重視する場合に最適です。従来のNgRxは、厳密な監査証跡、複雑な副作用、DevToolsによる詳細なデバッグが必要なエンタープライズアプリケーションに適しています。

アーキテクチャの根本的な違い

従来のNgRxはReduxパターンに従います。アクションがイベントを記述し、リデューサーが新しい不変状態を生成し、セレクターがデータを抽出します。すべての状態変更は予測可能で追跡可能なパイプラインを通過します。

Signal Storeは異なるアプローチを採用しています。アクションをリデューサー経由でディスパッチする代わりに、状態はリアクティブなシグナルとして存在します。更新はpatchState()を呼び出すメソッドを通じて行われ、派生値はメモ化されたセレクターではなく計算シグナルから取得されます。

| 観点 | 従来のNgRx | Signal Store | |------|------------|---------------| | メンタルモデル | Redux/Flux | リアクティブサービス | | ボイラープレート | 多い(アクション、リデューサー、セレクター、エフェクト) | 少ない(状態、メソッド、computed) | | リアクティビティ | RxJS Observables | Angular Signals | | DevTools | 完全なRedux DevTools | Signal Store DevTools(プラグイン経由) | | 学習曲線 | 急峻 | 中程度 | | バンドルサイズ | 大きい | 小さい |

Angular Signalsに慣れている開発者にとって、Signal Storeはフレームワークのリアクティブプリミティブの自然な拡張として感じられます。

従来のNgRx:AngularにおけるReduxパターン

従来のNgRxは、アクション、リデューサー、セレクター、エフェクトを中心に状態管理を構成します。この分離は冗長性と引き換えに予測可能性を強制します。

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 }>(),
  },
});

createActionGroup APIは関連するアクションを単一のソースの下にグループ化し、DevToolsの可読性を向上させ、個別の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,
  }))
);

リデューサーは新しい状態オブジェクトを返す純粋関数のままです。on()関数はアクションのマッチングと状態更新を簡潔に処理します。

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)
);

セレクターは自動的にメモ化を提供します。selectPublishedBooksセレクターはbooks配列が変更された場合にのみ再計算されるため、頻繁に更新されるストアでも読み取りが効率的です。

NgRx Signal Store:シグナルファーストの状態管理

Signal Storeは状態、計算値、メソッドを単一の注入可能なサービスに統合します。公式NgRxドキュメントでは、「Angular Signalsをネイティブにサポートするフル機能の状態管理ソリューション」と説明されています。

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),
      }));
    },
  }))
);

機能全体の状態管理が1つのファイルに収まります。状態プロパティは自動的にシグナルになり、計算シグナルがセレクターを置き換え、メソッドがアクションディスパッチを置き換えます。

withEntitiesによるエンティティ管理

両アプローチともエンティティコレクションをサポートしていますが、Signal StoreのwithEntities機能はコード量を劇的に削減します。

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));
    },
  }))
);

withEntities機能はidsentityMapentitiesシグナルを自動的に提供します。addEntityremoveEntityupdateEntityなどのエンティティアップデーターがコレクションの変更を処理します。

Angularの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

再利用性のためのカスタムストア機能

Signal StoreのsignalStoreFeature関数により、横断的な関心事を抽出してストア間で再利用できます。

with-request-status.tstypescript
import { computed } from '@angular/core';
import { signalStoreFeature, withComputed, withState } 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'),
    }))
  );
}

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

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

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

このカスタム機能は、ローディング状態の追跡が必要な任意のストアに組み込むことができます。

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());
      }
    },
  }))
);

合成されたストアは、ロジックを重複させることなくisPending()isFulfilled()isError()の計算シグナルを公開します。

コンポーネント統合パターン

Signal Storeはシグナルの読み取りを通じてAngularのテンプレート構文と自然に統合されます。

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">Loading books...</div>
    }

    @if (store.isError()) {
      <div class="error-message">Failed to load books</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)">Remove</button>
      </div>
    }

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

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

サブスクリプション管理は不要です。asyncパイプも不要です。シグナルはAngularのゾーンレス変更検知と統合され、最適なパフォーマンスを実現します。

パフォーマンスとバンドルサイズの考慮事項

Signal Storeの軽量なアーキテクチャは、より小さなバンドルに変換されます。コアの@ngrx/signalsパッケージはgzip圧縮で約4KBを追加しますが、完全な従来のNgRxスタック(@ngrx/store@ngrx/effects@ngrx/entity)は15〜20KBです。

Signal Storeは、Angularのシグナルベースの変更検知からも恩恵を受けます。計算シグナルは依存関係が変更された場合にのみ再計算され、コンポーネントは消費されたシグナルが更新された場合にのみ再レンダリングされます。このきめ細かいリアクティビティにより、Observableベースのパターンで一般的な不要な変更検知サイクルが排除されます。

RxJSを多用しているアプリケーションでは、従来のNgRxのObservableベースのセレクターが既存のパイプラインとシームレスに統合されます。

従来型からSignal Storeへの移行戦略

既存の従来型NgRxアプリケーションを移行する際、ビッグバン的な書き換えは必要ありません。両方のソリューションは共存でき、段階的な採用が可能です。

  • 新機能はSignal Storeを使用して開始する
  • 分離された機能モジュールを1つずつ移行する
  • 移行コストが利点を上回る場合は、複雑でエフェクトの多い機能を従来のNgRxに維持する
  • NgRx Signalsの相互運用ユーティリティを使用して、Observableベースとシグナルベースの状態間をブリッジする
共存は機能する

従来のNgRx StoreとSignal Storeは、同じアプリケーション内で並行して実行できます。NgRxチームは両方のアプローチを公式にサポートし、両者間のブリッジのための相互運用ユーティリティを提供しています。

各アプローチを選択すべき場合

Signal Storeを選択すべき場合:

  • Angular 20以降の新規アプリケーションを構築する場合
  • 機能レベルまたはコンポーネントレベルの状態を管理する場合
  • 開発者体験とボイラープレート削減を優先する場合
  • チームがNgRxの経験が限られている場合
  • バンドルサイズが重要な場合

従来のNgRxを選択すべき場合:

  • 既存のNgRxアプリケーションを保守する場合
  • 厳密なアクションログと監査証跡が必要な場合
  • 広範な副作用を伴う複雑なワークフローを構築する場合
  • 完全なRedux DevToolsタイムトラベルデバッグが必要な場合
  • チームが深いNgRxの専門知識を持っている場合

状態管理の基礎を理解することで、開発者は多様なAngularコードベースに対応する準備ができます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

まとめ

  • Signal Storeは、従来のNgRxと比較して状態管理のボイラープレートを60〜70%削減します
  • 従来のNgRxは、厳密な監査証跡と複雑な副作用を必要とするエンタープライズアプリケーションにとってより良い選択肢です
  • 両方のアプローチは同じアプリケーション内で共存でき、段階的な移行が可能です
  • Signal Storeの計算シグナルは、明示的なセレクター定義なしで自動メモ化を提供します
  • カスタムストア機能(signalStoreFeature)により、再利用可能な横断的関心事が実現されます
  • 2026年のAngular 20以降の新規プロジェクトでは、特定の要件が従来のNgRxを必要としない限り、Signal Storeをデフォルトの選択肢にすべきです

共有

関連記事