Управление состоянием с Akita в Angular
Зачем вообще нужно управлять состоянием?
Веб-приложения постоянно меняют свое состояние. Например, когда пользователь вводит текст в поле формы, выбирает элемент в списке или открывает новую страницу - состояние приложения обновляется.
Чтобы отслеживать все эти изменения, нужен специальный механизм управления состоянием. Он позволяет:
- Хранить данные в определенном месте (например, в специальных объектах - сторах)
- Обновлять эти данные по определенным правилам
- Получать актуальные данные в любой момент
- Автоматически обновлять UI при изменении данных
- Отменять изменения и возвращать предыдущее состояние
Без управления состоянием код быстро превращается в хаос из глобальных переменных, сложных зависимостей и дублирования данных. Поэтому использование спецальных инструментов вроде Akita - это очень хорошая практика.
Что такое Akita?
Akita - это библиотека для управления состоянием в Angular-приложениях. Вот её основные преимущества:
- Простота. Интуитивный и лаконичный API.
- Надежность. Строго типизированное состояние, иммутабельность.
- Гибкость. Кастомизируй под свои нужды.
- Производительность. Оптимизирована для скорости.
- Масштабируемость. Подходит как для маленьких, так и для крупных приложений.
То есть с Akita ты можешь быстро организовать управление состоянием в своём приложении и не париться по поводу производительности или масштабирования. Дальше разберём как это делается.
Установка Akita
Чтобы начать использовать Akita, нужно выполнить deux простых шага:
npm install @datorama/akita --save npm install @datorama/akita-ng-entity-service --save
2. Импортировать модули Akita в app.module.ts:
import { AkitaNgDevtools } from '@datorama/akita-ngdevtools'; import { AkitaNgRouterStoreModule } from '@datorama/akita-ng-router-store'; import { AkitaTypedFormModule } from '@datorama/akita-ng-forms'; @NgModule({ declarations: [AppComponent], imports: [ AkitaNgDevtools.forRoot(), AkitaNgRouterStoreModule.forRoot(), AkitaTypedFormModule ], bootstrap: [AppComponent] }) export class AppModule {}
Вот и всё! Теперь Akita готова к использованию в твоём приложении. Давай разберём как это делать на практике.
Создание Store
Основа управления состоянием в Akita - это Store. Store похож на обычный JavaScript объект, но с некоторыми дополнительными свойствами:
- Хранит данные в строго типизированном виде
- Позволяет изменять данные только через специальные методы
- Может отслеживать изменения и выполнять действия при обновлении
- Даёт доступ к данным через удобные запросы
Давай создадим простой Store для хранения списка товаров в корзине:
import { Injectable } from '@angular/core'; import { Store, StoreConfig } from '@datorama/akita'; export interface CartState { items: string[]; } export function createInitialState(): CartState { return { items: [] }; } @Injectable({ providedIn: 'root' }) @StoreConfig({ name: 'cart' }) export class CartStore extends Store<CartState> { constructor() { super(createInitialState()); } }
- Описываем интерфейс для состояния
- Создаём initialState - начальное состояние
- Создаём класс Store, наследуя от базового класса Akita
- В конструктор передаём начальное состояние
Теперь у нас есть store для хранения данных о корзине! Давай посмотрим как им пользоваться.
Действия и мутации
Чтобы изменить данные в store, нужно выполнить действие и совершить мутацию.
Действие - это просто функция, которая описывает какие изменения нужно сделать.
Например, добавим действие для добавления товара в корзину:
export class CartStore extends Store<CartState> { addItem(item: string) { this.update(state => { return { items: [...state.items, item] }; }); } }
Мутация - это функция, которая на самом деле модифицирует состояние.
В нашем случае это метод update
из базового класса Store. Он принимает callback, который возвращает новое состояние.
Таким образом мы добавляем новый товар в массив items
. При этом старый массив не мутируется, т.к создаётся новый с использованием оператора spread. Это очень важно для производительности и предсказуемости!
Чтобы вызвать это действие, делаем так:
this.cartStore.addItem('Новый товар');
Теперь данные в store обновятся иммутабельным способом!
Запросы к хранилищу
Чтобы получить данные из store, используются селекторы.
Селектор - это просто функция, которая принимает состояние и возвращает нужную часть:
export class CartStore extends Store<CartState> { items$ = this.select(state => state.items); }
А в компоненте достаточно подписаться на этот селектор:
constructor(private cartStore: CartStore) {} ngOnInit() { this.items$ = this.cartStore.items$; }
И использовать в шаблоне через async pipe:
<div *ngFor="let item of items$ | async"> {{ item }} </div>
Akita автоматически отследит изменения в store и обновит данные в шаблоне! Очень удобно и эффективно.
Асинхронные операции
А что если нужно выполнить асинхронный запрос при обновлении store? Например, сохранить корзину на сервере?
Для этого есть метод setLoading(true)
который позволяет отследить загрузку:
saveCart() { this.cartStore.setLoading(true); this.http.post('/save', this.cartStore.getValue()).pipe( tap(() => this.cartStore.setLoading(false)) ); }
А в компоненте можно отслеживать loading$:
<div *ngIf="loading$ | async"> Saving...</div>
Таким образом мы можем элегантно обрабатывать асинхронные операции и обновлять UI accordingly.
Пример использования в проекте
Давай я покажу как мы использовали Akita в одном из своих проектов.
У нас был интернет магазин, и нам нужно было реализовать корзину и списки желаний. Вот как мы это сделали:
1. Создали CartStore для хранения товаров в корзине:
export interface CartState { items: CartItem[]; } export class CartStore extends Store<CartState> { // ...действия, мутации, запросы }
2. Создали WishListStore для хранения избранных товаров:
export interface WishListState { items: string[]; } export class WishListStore extends Store<WishListState> { // ... }
3. В сервисе для работы с API делали запросы и обновляли store:
updateCart(items) { this.cartStore.update(state => ({ items })); } addToWishList(product) { this.wishListStore.addItem(product); }
4. В компонентах подписывались на нужные селекторы и отображали данные.
5. Для общих данных использовали селекторы из EntityService.
Таким образом мы разделили всю логику на небольшие части и создали чистое управление состоянием!
Итог
- Зачем нужно управлять состоянием в приложениях
- Как использовать Akita для этого в Angular
- Как создавать Store, выполнять действия и мутации
- Как делать запросы и обрабатывать асинхронные операции
- Пример интеграции Akita в реальном проекте
Главное - Akita позволяет организовать предсказуемое и эффективное управление состоянием без лишней сложности. А это очень важно для современных веб-приложений! Надеюсь, эта статья была полезна и понятна.