NgRx Signal Store vs Класичний NgRx у 2026: Який Обрати?

Комплексне порівняння NgRx Signal Store та класичного NgRx для управління станом в Angular. Практичні приклади коду та рекомендації на 2026 рік.

NgRx Signal Store vs Classic NgRx порівняння

NgRx Signal Store представляє фундаментальний зсув в управлінні станом Angular, замінюючи традиційний патерн Redux (екшени-редюсери-селектори) реактивним підходом на основі сигналів. Це порівняння досліджує обидва рішення, щоб допомогти розробникам Angular приймати обґрунтовані архітектурні рішення у 2026 році.

Швидкий посібник з прийняття рішень

Використовуйте Signal Store для стану на рівні фіч та застосунків середньої складності, де важливе зменшення бойлерплейту. Використовуйте Класичний NgRx для корпоративних застосунків, що вимагають суворих аудиторських слідів, складних побічних ефектів та розширеного налагодження в DevTools.

Розуміння Архітектурних Відмінностей

Класичний NgRx базується на патерні Redux: екшени описують події, редюсери створюють новий незмінний стан, а селектори витягують дані. Кожна зміна стану проходить через передбачуваний, відстежуваний конвеєр.

Signal Store використовує інший підхід. Замість диспатчингу екшенів через редюсери, стан живе в реактивних сигналах. Оновлення відбуваються через методи, що викликають patchState(), а похідні значення отримуються з computed signals замість мемоїзованих селекторів.

| Аспект | Класичний NgRx | Signal Store | |--------|---------------|---------------| | Ментальна модель | Redux/Flux | Реактивні сервіси | | Бойлерплейт | Високий (екшени, редюсери, селектори, ефекти) | Низький (стан, методи, computed) | | Реактивність | RxJS Observables | Angular Signals | | DevTools | Повні Redux DevTools | Signal Store DevTools (через плагін) | | Крива навчання | Крута | Помірна | | Розмір бандлу | Більший | Менший |

Для розробників, вже знайомих з Angular Signals, Signal Store відчувається як природне розширення реактивних примітивів фреймворку.

Класичний NgRx: Патерн Redux в Angular

Класичний 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 }>(),
  },
});

API createActionGroup групує пов'язані екшени під одним джерелом, покращуючи читабельність у 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 перераховується лише коли змінюється масив книг, роблячи читання ефективним навіть у store, що часто оновлюються.

NgRx Signal Store: Управління Станом на Основі Сигналів

Signal Store консолідує стан, computed значення та методи в єдиний ін'єктований сервіс. Офіційна документація 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),
      }));
    },
  }))
);

Все управління станом фічі вміщується в одному файлі. Властивості стану автоматично стають сигналами, computed signals замінюють селектори, а методи замінюють диспатчинг екшенів.

Управління Сутностями з withEntities

Обидва підходи підтримують колекції сутностей, але функція withEntities в Signal Store драматично зменшує кількість коду.

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 автоматично надає сигнали ids, entityMap та entities. Оновлювачі сутностей як addEntity, removeEntity та updateEntity обробляють мутації колекцій.

Готовий до співбесід з Angular?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Власні Фічі Store для Повторного Використання

Функція signalStoreFeature в Signal Store дозволяє витягувати та повторно використовувати наскрізну функціональність у різних store.

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' };
}

Цю власну фічу тепер можна скомпонувати в будь-який store, що потребує відстеження стану завантаження.

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

Скомпонований store тепер надає computed signals 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">Завантаження книг...</div>
    }

    @if (store.isError()) {
      <div class="error-message">Не вдалося завантажити книги</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)">Видалити</button>
      </div>
    }

    <p>Всього: {{ store.totalBooks() }} книг</p>
  `,
})
export class BooksListComponent {
  readonly store = inject(BooksStore);

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

Немає управління підписками. Немає async pipes. Сигнали інтегруються з zoneless change detection Angular для оптимальної продуктивності.

Продуктивність та Розмір Бандлу

Легша архітектура Signal Store перетворюється на менші бандли. Основний пакет @ngrx/signals додає приблизно 4KB gzip, порівняно з 15-20KB для повного стеку класичного NgRx (@ngrx/store, @ngrx/effects, @ngrx/entity).

Signal Store також отримує вигоду від change detection Angular на основі сигналів. Computed signals перераховуються лише коли змінюються залежності, а компоненти перерендерюються лише коли оновлюються споживані сигнали. Ця гранулярна реактивність усуває непотрібні цикли change detection, типові для патернів на основі Observable.

Для застосунків, що вже інтенсивно використовують RxJS, селектори класичного NgRx на основі Observable безперешкодно інтегруються з існуючими конвеєрами.

Стратегія Міграції з Класичного до Signal Store

Міграція існуючого застосунку класичного NgRx не вимагає повного переписування. Обидва рішення можуть співіснувати, дозволяючи поступове впровадження.

  • Почати нові фічі з використанням Signal Store
  • Мігрувати ізольовані модулі фіч по одному
  • Залишити складні фічі з великою кількістю ефектів на класичному NgRx, якщо вартість міграції перевищує користь
  • Використовувати утиліти NgRx Signals interop для з'єднання стану на основі Observable та стану на основі сигналів
Співіснування працює

Класичний NgRx Store та Signal Store можуть працювати поруч в одному застосунку. Команда NgRx офіційно підтримує обидва підходи та надає утиліти для взаємодії між ними.

Коли Обирати Кожен Підхід

Обирайте Signal Store коли:

  • Створення нових застосунків Angular 20+
  • Управління станом на рівні фіч або компонентів
  • Пріоритетом є developer experience та зменшення бойлерплейту
  • Команда має обмежений досвід з NgRx
  • Важливий розмір бандлу

Обирайте класичний NgRx коли:

  • Підтримка існуючих застосунків NgRx
  • Потрібні суворі логи екшенів та аудиторські сліди
  • Створення складних робочих процесів з розширеними побічними ефектами
  • Потрібне повне time-travel налагодження Redux DevTools
  • Команда має глибоку експертизу NgRx

Розуміння основ управління станом в обох підходах готує розробників до роботи з різноманітними кодовими базами Angular.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Висновок

  • Signal Store зменшує бойлерплейт управління станом на 60-70% порівняно з класичним NgRx
  • Класичний NgRx залишається кращим вибором для корпоративних застосунків, що вимагають суворих аудиторських слідів та складних побічних ефектів
  • Обидва підходи можуть співіснувати в одному застосунку, дозволяючи поступову міграцію
  • Computed signals Signal Store забезпечують автоматичну мемоїзацію без явних визначень селекторів
  • Власні фічі store (signalStoreFeature) дозволяють повторно використовувати наскрізну функціональність
  • Для нових проектів Angular 20+ у 2026 році Signal Store має бути вибором за замовчуванням, якщо специфічні вимоги не потребують класичного NgRx

Теги

#angular
#ngrx
#state-management
#signals
#typescript

Поділитися

Пов'язані статті

RxJS в Angular 2026 оператори, Subject'и та interop із Signals

RxJS в Angular 2026: оператори, Subject'и та interop із Signals

RxJS в Angular 2026: опануйте оператори, Subject'и та патерни interop із Signals, які розробники використовують у продакшені, плюс найпоширеніші питання співбесід.

Angular 20 Resource API та httpResource: посібник з прикладами коду та питаннями для співбесід

Angular 20 Resource API, httpResource та питання для технічних співбесід

Детальний посібник з Resource API в Angular 20: resource(), rxResource() та httpResource() для реактивного отримання даних. Валідація Zod, рядкові статуси, міграція з HttpClient та актуальні питання для технічних співбесід Angular у 2026 році.

Питання співбесіди Angular 19: Signals, SSR та інкрементальна гідратація

Питання співбесіди Angular 19: Signals, SSR і обов'язкові концепції

Найпоширеніші питання співбесіди з Angular 19: Signals, інкрементальна гідратація, zoneless change detection і нові реактивні API з прикладами коду та очікуваними відповідями.