State management in Angular is a tricky beast. If you're working on a serious Angular project, you've probably heard of NgRx—the go-to library for managing state in large applications. But let's be honest, NgRx comes with a lot of boilerplate.
This is where Akita steps in. It’s simpler, faster, and less painful. In this post, we’ll break down the differences between NgRx and Akita, and show you why Akita often wins the battle for state management in Angular apps.
NgRx: The Good, The Bad, and The Ugly
NgRx is inspired by Redux, bringing predictable state management with Actions, Reducers, Selectors, and Effects. It’s powerful, but it comes at a cost—tons of boilerplate.
What’s Good About NgRx?
✔ Strict state management: Enforces immutability and one-way data flow.
✔ Great for large-scale apps: Best suited for highly structured applications.
✔ Strong ecosystem: Well-documented, widely adopted, and has devtools support.
What’s Bad About NgRx?
❌ Too much boilerplate: Actions, reducers, effects—so many files just to update a single piece of state.
❌ Steep learning curve: It takes time to get comfortable with all the concepts.
❌ Performance overhead: Every state update goes through a reducer and effect, which can slow things down.
Akita: The Better Way to Manage State
Akita is a lightweight state management library designed to be simple and intuitive. Unlike NgRx, it doesn’t require tons of boilerplate code. It follows a store-query-service pattern, which is much easier to grasp than NgRx’s full Redux-like setup.
What’s Good About Akita?
✔ Less boilerplate: No need for actions, reducers, or effects—just update the store directly.
✔ Easy to learn: Simple API that makes managing state fun instead of frustrating.
✔ Built-in Entity Store: Perfect for managing collections of data (like users, products, or orders).
✔ Faster performance: No extra processing overhead from actions and reducers.
✔ Mutable updates (but safe): You can modify state directly, and Akita ensures immutability under the hood.
Where Akita Wins Over NgRx
Code Comparison: NgRx vs Akita
Let’s say we have a User Store. Here’s how you do it in NgRx vs Akita.
NgRx Implementation (Too Much Code!)
// actions/user.actions.ts
import { createAction, props } from '@ngrx/store';
export const setUser = createAction('[User] Set', props<{ name: string; age: number }>());
// reducers/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { setUser } from './user.actions';
export interface UserState { name: string; age: number; }
const initialState: UserState = { name: '', age: 0 };
export const userReducer = createReducer(initialState,
on(setUser, (state, { name, age }) => ({ ...state, name, age })));
// selectors/user.selectors.ts
import { createSelector } from '@ngrx/store';
export const selectUser = (state) => state.user;
// effects/user.effects.ts (if calling an API)
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { setUser } from './user.actions';
import { tap } from 'rxjs/operators';
@Injectable()
export class UserEffects {
updateUser$ = createEffect(() => this.actions$.pipe(
ofType(setUser),
tap((action) => console.log('User updated:', action))
), { dispatch: false });
constructor(private actions$: Actions) {}
}
Way too much code just to update a user’s state! 🚨
Akita Implementation (So Much Simpler!)
// user.store.ts
import { Store, StoreConfig } from '@datorama/akita';
export interface UserState { name: string; age: number; }
export function createInitialState(): UserState { return { name: '', age: 0 }; }
@StoreConfig({ name: 'user' })
export class UserStore extends Store<UserState> {
constructor() { super(createInitialState()); }
}
// user.service.ts
import { Injectable } from '@angular/core';
import { UserStore } from './user.store';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private userStore: UserStore) {}
updateUser(name: string, age: number) {
this.userStore.update({ name, age });
}
}
// user.query.ts
import { Query } from '@datorama/akita';
import { UserState, UserStore } from './user.store';
export class UserQuery extends Query<UserState> {
user$ = this.select();
constructor(protected store: UserStore) { super(store); }
}
// user.component.ts
import { Component } from '@angular/core';
import { UserQuery } from './user.query';
import { UserService } from './user.service';
@Component({
selector: 'app-user',
template: `<div *ngIf="user$ | async as user">{{ user.name }} ({{ user.age }})</div>
<button (click)="updateUser()">Update</button>`
})
export class UserComponent {
user$ = this.userQuery.user$;
constructor(private userQuery: UserQuery, private userService: UserService) {}
updateUser() { this.userService.updateUser('Emran', 30); }
}
Less code. Fewer files. No unnecessary complexity. 🎉
Final Thoughts: Should You Choose Akita or NgRx?
👉 Use **NgRx **if you're working on a large-scale enterprise app with strict requirements and multiple developers.
👉 Use **Akita **if you want a simple, scalable, and easy-to-maintain state management solution.
If you’re tired of boilerplate and just want to get things done faster, Akita is the way to go. 🔥
Top comments (0)