NgRx Signal Store vs 従来型NgRx(2026年版):どちらを選ぶべきか
NgRx Signal Storeと従来のNgRxを比較し、Angular状態管理の選択方法を解説します。実践的なコード例付き。

NgRx Signal Storeは、Angular状態管理における根本的なパラダイムシフトを象徴しています。従来のReduxスタイルのアクション・リデューサー・セレクターパターンから、シグナルベースのリアクティブアプローチへと移行しました。NgRx 22(2026年8月リリース)では、リソース拡張機能、動的ディープシグナル、改善されたコンポーザビリティが追加されました。本記事では、Angular開発者が適切なアーキテクチャを選択できるよう、両ソリューションを詳細に比較します。
Signal Storeは、機能レベルの状態管理、中規模の複雑さを持つアプリケーション、AngularネイティブのResource API統合に最適です。従来のNgRxは、厳密な監査証跡、複雑な副作用、DevToolsによる詳細なデバッグが必要なエンタープライズアプリケーションに適しています。
アーキテクチャの根本的な違い
従来のNgRxはReduxパターンに従います。アクションがイベントを記述し、リデューサーが新しい不変状態を生成し、セレクターがデータを抽出します。すべての状態変更は予測可能で追跡可能なパイプラインを通過します。
Signal Storeは異なるアプローチを採用しています。アクションをリデューサー経由でディスパッチする代わりに、状態はリアクティブなシグナルとして存在します。更新はpatchState()を呼び出すメソッドを通じて行われ、派生値はメモ化されたセレクターではなく計算シグナルから取得されます。
| 観点 | 従来のNgRx | Signal Store |
|---|---|---|
| メンタルモデル | Redux/Flux | リアクティブサービス |
| ボイラープレート | 多い(アクション、リデューサー、セレクター、エフェクト) | 少ない(状態、メソッド、computed) |
| リアクティビティ | RxJS Observables | Angular Signals |
| Resource API | 手動統合 | @ngrx/signals/resourceによるネイティブ対応 |
| DevTools | 完全なRedux DevTools | Signal Store DevTools(プラグイン経由) |
| 学習曲線 | 急峻 | 中程度 |
| バンドルサイズ | 大きい(gzip圧縮で約15〜20KB) | 小さい(gzip圧縮で約4KB) |
Angular Signalsに慣れている開発者にとって、Signal Storeはフレームワークのリアクティブプリミティブの自然な拡張として感じられます。
従来のNgRx:AngularにおけるReduxパターン
従来のNgRxは、アクション、リデューサー、セレクター、エフェクトを中心に状態管理を構成します。この分離は冗長性と引き換えに予測可能性を強制します。
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呼び出しと比較してボイラープレートを削減します。
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()関数はアクションのマッチングと状態更新を簡潔に処理します。
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をネイティブにサポートするフル機能の状態管理ソリューション」と説明されています。
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つのファイルに収まります。状態プロパティは自動的にシグナルになり、計算シグナルがセレクターを置き換え、メソッドがアクションディスパッチを置き換えます。
NgRx 22:リソース拡張機能とwithProps
NgRx 22では@ngrx/signals/resourceエントリポイントが導入され、AngularのResource APIとの直接統合が可能になりました。withProps機能によりサービス注入が一元化され、リソース拡張機能が一般的な非同期パターンを処理します。
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' },
// withPropsでサービスを注入(アンダースコアでプライベートを示す規約)
withProps(() => ({
_booksService: inject(BooksService),
})),
// 拡張機能付きでリソースを設定
withProps((store) => ({
_booksResource: extendResource(
httpResource<Book[]>({
url: '/api/books',
defaultValue: [],
}),
withPreviousValueOnLoading(), // リロード中に前の値を保持
withValueOnError([]) // エラー時に空配列を返す
),
})),
// 読み取り専用リソースをコンシューマーに公開
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)
),
}))
);リソース拡張機能は2つの一般的な問題を解決します。リロード中のちらつき(withPreviousValueOnLoadingで解決)とエラー状態の処理(withValueOnErrorまたはwithPreviousValueOnErrorで解決)です。これらの拡張機能は組み合わせ可能で、provideResourceExtensionsを通じてグローバルに提供することもできます。
withEntitiesによるエンティティ管理
両アプローチともエンティティコレクションをサポートしていますが、Signal StoreのwithEntities機能はコード量を劇的に削減します。
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機能はids、entityMap、entitiesシグナルを自動的に提供します。addEntity、removeEntity、updateEntityなどのエンティティアップデーターがコレクションの変更を処理します。
Angularの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
再利用性のためのカスタムストア機能
Signal StoreのsignalStoreFeature関数により、横断的な関心事を抽出してストア間で再利用できます。NgRx 22ではSignalStoreFeatureTypeが追加され、型の抽出が簡素化されました。
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:手動で再宣言せずに型を抽出
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' };
}このカスタム機能は、ローディング状態の追跡が必要な任意のストアに組み込むことができます。
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()の計算シグナルを公開します。
watchStateによる同期的な状態追跡
NgRx 22では、同期的な状態変更追跡のためのwatchState関数が追加されました。元に戻す/やり直し機能や状態履歴に有用です。
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[] = [];
// 同期追跡:すべての状態変更をキャプチャ、合体なし
watchState(store, (state) => {
history.push(structuredClone(state));
});
return { history };
}更新を合体させるエフェクトベースの追跡とは異なり、watchStateは各patchState呼び出しで同期的に発火し、完全な状態履歴をキャプチャします。
コンポーネント統合パターン
Signal Storeはシグナルの読み取りを通じてAngularのテンプレート構文と自然に統合されます。
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のゾーンレス変更検知と統合され、最適なパフォーマンスを実現します。Angular 22ではOnPushがデフォルトの変更検知戦略となり、Signal Storeコンポーネントは精密できめ細かい更新の恩恵を受けます。
パフォーマンスとバンドルサイズの考慮事項
Signal Storeの軽量なアーキテクチャは、より小さなバンドルに変換されます。コアの@ngrx/signalsパッケージはgzip圧縮で約4KBを追加しますが、完全な従来のNgRxスタック(@ngrx/store、@ngrx/effects、@ngrx/entity)は15〜20KBです。
Signal Storeは、Angularのシグナルベースの変更検知からも恩恵を受けます。計算シグナルは依存関係が変更された場合にのみ再計算され、コンポーネントは消費されたシグナルが更新された場合にのみ再レンダリングされます。このきめ細かいリアクティビティにより、Observableベースのパターンで一般的な不要な変更検知サイクルが排除されます。
RxJSを多用しているアプリケーションでは、従来のNgRxのObservableベースのセレクターが既存のパイプラインと問題なく統合されます。
NgRx 22にはAngular 22.x、TypeScript 6.0.x、RxJS 6.5.xまたは7.5.xが必要です。Signal Storeプロジェクトの場合はng update @ngrx/signals@22で更新してください。
従来型からSignal Storeへの移行戦略
既存の従来型NgRxアプリケーションを移行する際、ビッグバン的な書き換えは必要ありません。両方のソリューションは共存でき、段階的な採用が可能です。
- 新機能はSignal Storeを使用して開始する
- 分離された機能モジュールを1つずつ移行する
- 移行コストが利点を上回る場合は、複雑でエフェクトの多い機能を従来のNgRxに維持する
- NgRx Signalsの相互運用ユーティリティを使用して、Observableベースとシグナルベースの状態間をブリッジする
従来のNgRx StoreとSignal Storeは、同じアプリケーション内で並行して実行できます。NgRxチームは両方のアプローチを公式にサポートし、両者間のブリッジのための相互運用ユーティリティを提供しています。
各アプローチを選択すべき場合
Signal Storeを選択すべき場合:
- Angular 22以降の新規アプリケーションを構築する場合
- 機能レベルまたはコンポーネントレベルの状態を管理する場合
- AngularのResource APIやSignal Formsと統合する場合
- 開発者体験とボイラープレート削減を優先する場合
- チームがNgRxの経験が限られている場合
- バンドルサイズが重要な場合
従来のNgRxを選択すべき場合:
- 既存のNgRxアプリケーションを保守する場合
- 厳密なアクションログと監査証跡が必要な場合
- 広範な副作用を伴う複雑なワークフローを構築する場合
- 完全なRedux DevToolsタイムトラベルデバッグが必要な場合
- チームが深いNgRxの専門知識を持っている場合
状態管理の基礎を理解することで、開発者は多様なAngularコードベースに対応する準備ができます。
Sources
- NgRx v22 Release Announcement - リソース拡張機能、動的ディープシグナル、SignalStoreFeatureType
- Angular 22 Feature Overview - Signal Forms安定版、OnPushデフォルト、Resource API
- Using Angular's Resource API with NgRx Signal Store - withPropsパターン、httpResource統合
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
Signal Store vs 従来のNgRx:重要なポイント
- Signal Storeは、従来のNgRxと比較して状態管理のボイラープレートを60〜70%削減する
- NgRx 22はリソース拡張機能(
withPreviousValueOnLoading、withValueOnError)を追加し、非同期状態処理を効率化する withProps機能によりサービス注入が一元化され、フィーチャー間でのパラメータリストの重複が排除されるwatchStateにより、元に戻す/やり直しや状態履歴のための同期的な状態追跡が可能になる- 従来のNgRxは、厳密な監査証跡と複雑な副作用を必要とするエンタープライズアプリケーションにとってより良い選択肢である
- 両方のアプローチは同じアプリケーション内で共存でき、段階的な移行が可能である
- Angular 22以降の新規プロジェクトでは、特定の要件が従来のNgRxを必要としない限り、Signal Storeをデフォルトの選択肢にすべきである
Angular のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年10月9日 更新
共有
関連記事

2026年フリーランスAngular開発者の仕事:案件の探し方、単価相場、面接対策
2026年のフリーランスAngular開発者案件市場を詳細に解説。日単価の相場(中央値440ユーロ)、案件の探し方、面接対策、契約形態の違いについて、最新データをもとに紹介します。

2026年のAngular開発者求人:求人の探し方、給与レンジ、準備方法
2026年のAngular開発者向け求人市場を解説。求人の探し方、年収・日給レンジ、リモートワークの現状、面接対策のポイントを紹介します。

Angular SignalsとComputedで実現するきめ細かなリアクティビティ:2026年の技術面接対策
Angular 20以降で標準となったSignalsとComputed、linkedSignalの仕組みを解説。技術面接で問われるリアクティビティの設計パターンとRxJSとの使い分けを詳しく説明します。