Какие подходы к управлению состоянием вы предпочитаете (NgRx, Akita, Signals)?

Подход к управлению состоянием в Angular зависит от масштаба проекта, требований к предсказуемости, отладки, количества источников данных и необходимости в реактивности. На практике я использую разные подходы: от простого сервисного слоя с BehaviorSubject, до полной архитектуры с NgRx, Akita или Signals. Ниже описаны предпочтения и сценарии использования каждого подхода.

1. Простой сервис с BehaviorSubject

Для небольших приложений или фич, которые не требуют сложного управления состоянием, использую обычные Angular-сервисы с BehaviorSubject или ReplaySubject.

Пример:

@Injectable({ providedIn: 'root' })
export class AuthService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
setUser(user: User) {
this.userSubject.next(user);
}
logout() {
this.userSubject.next(null);
}
}

Плюсы:

  • Простота реализации;

  • Хорошо работает в изолированных модулях;

  • Нет лишнего boilerplate.

Минусы:

  • Нет строгой структуры;

  • Не масштабируется при росте приложения;

  • Нет devtools/таймтревела.

2. NgRx

Использую NgRx в крупных, корпоративных приложениях, где:

  • Много взаимодействий с сервером;

  • Требуется строгое разделение ответственности;

  • Важна предсказуемость и отладка состояний (DevTools);

  • Сложная бизнес-логика;

  • Необходим таймтревел, undo-redo, state replay.

Пример структуры:

store/
├── auth/
 ├── auth.actions.ts
 ├── auth.effects.ts
 ├── auth.reducer.ts
 └── auth.selectors.ts
├── user/
└── app.state.ts

Плюсы:

  • Предсказуемость и однонаправленный поток данных;

  • Интеграция с Effects для обработки побочных эффектов;

  • Инструменты разработки (Redux Devtools);

  • Строгая архитектура;

  • Возможность time-travel, логирования и записи состояния.

Минусы:

  • Много шаблонного кода (boilerplate);

  • Крутая кривая обучения;

  • Чрезмерно тяжёлый для простых приложений.

С появлением NgRx Component Store возможно использовать NgRx в локальном скоупе без глобального store, что удобно для feature-модулей.

3. Akita

Использую Akita, когда нужна реактивная архитектура с меньшим количеством шаблонного кода, но при этом требуется централизованное управление состоянием с разветвлённой логикой и селекторами.

Пример:

@Injectable({ providedIn: 'root' })
export class TodoStore extends EntityStore<TodoState, Todo> {
constructor() {
super();
}
}

Плюсы:

  • Менее шаблонный и более декларативный код по сравнению с NgRx;

  • Простота создания EntityStore;

  • Отличная документация;

  • Локальные или глобальные сторы;

  • Поддержка transaction/undo/history.

Минусы:

  • Менее строгий подход;

  • Нет такой экосистемы и сообщества, как у NgRx;

  • Сложнее для команд, привыкших к Flux-архитектуре.

Akita отлично подходит для проектов, где есть rich-data model и нужно управлять большим количеством сущностей.

4. Signals + SignalStore (Angular 16+)

С выходом Signals (signal, computed, effect) и экспериментального @angular/signals/store появляется современная альтернатива RxJS и NgRx.

Пример:

export class CounterStore {
count = signal(0);
double = computed(() => this.count() \* 2);
increment() {
this.count.set(this.count() + 1);
}
}
Signal Store:
@Injectable()
export class TodosStore extends signalStore({ todos: \[\] }) {
readonly completed = this.select(s => s.todos.filter(t => t.completed));
add(todo: Todo) {
this.update(state => ({
todos: \[...state.todos, todo\]
}));
}
}

Плюсы:

  • Императивный стиль, ближе к JavaScript;

  • Высокая производительность;

  • Нет подписок, нет .subscribe();

  • Простой синтаксис;

  • Локальные или глобальные сторы;

  • Потенциальная замена NgRx/BehaviorSubject.

Минусы:

  • Пока не production-ready во всех аспектах;

  • Ограниченный инструментарий (DevTools, Debugging);

  • Меньшее количество обучающих материалов;

  • Возможна путаница с change detection на ранней стадии.

Подходит для новых проектов на Angular 17+ и для feature-модулей без необходимости в сложном state management.

5. Сравнение подходов

Подход Применимость Boilerplate Реактивность DevTools Сложность
BehaviorSubject Мелкие модули Низкий Да Нет Низкая
--- --- --- --- --- ---
NgRx Крупные проекты Высокий Да (RxJS) Да Высокая
--- --- --- --- --- ---
Akita Средние/крупные Средний Да (RxJS) Да Средняя
--- --- --- --- --- ---
Signals Новые модули, SPA Низкий Да (Signals) Ограничено Средняя
--- --- --- --- --- ---
NgRx Component Store Средние проекты Средний Да Да Средняя
--- --- --- --- --- ---

6. Комбинированный подход

В крупных приложениях я использую комбинацию:

  • Signals — для UI-логики или компонентов;

  • ComponentStore — для feature-модулей;

  • NgRx — для глобального состояния;

  • Akita — если проект исторически на нём и нужно управление сущностями.

При этом соблюдается принцип: "используй минимально необходимое" — чем меньше глобального состояния, тем легче поддержка.