DEV Community

Cover image for NgRx vs Akita: The State Management Battle
MD. Amran
MD. Amran

Posted on

NgRx vs Akita: The State Management Battle

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

Image description

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) {}
}
Enter fullscreen mode Exit fullscreen mode

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); }
}
Enter fullscreen mode Exit fullscreen mode

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. 🔥

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs