myHotTake

Tag: Akita state library

  • NgRx vs Akita vs MobX: Pros, Cons, and Use Cases

    Smash that like or share button if this story makes state management feel crystal clear! Here we go:


    I’m running a music band, as one does. Each player represents a part of my app. The drummer is the UI, the guitarist handles data fetching, and the vocalist keeps track of user input. To keep us in sync, I can choose one of three methods: NgRx, Akita, or MobX.

    NgRx is like hiring a strict conductor for my band. The conductor demands sheet music for every song (reducers and actions), and each note must be documented (strict immutability rules). This ensures we never miss a beat, but wow, it takes time to prep for every jam session. In the end, the band plays like clockwork—perfect for big shows (large apps)—but it can feel a bit stiff and over-structured when we just want to vibe quickly.

    Akita is like having a loose but effective band leader. They tell us the main chords and let us improvise within that structure. They’ve got this ability to tweak the sound mid-performance (mutable stores) without messing everything up. The band feels free to experiment and adapt while still sounding tight. It’s awesome for mid-sized gigs where flexibility is key, but it might not scale up as perfectly for massive arenas.

    MobX, though, is like we’re all talented solo artists with earpieces. I tell the guitarist, “Play softer,” and automatically the whole band adjusts. It’s magic! MobX handles changes in real time, effortlessly, with minimal prep. But if we don’t talk to each other (organize dependencies well), someone might hit the wrong note, and chaos ensues. Perfect for small, intimate shows, but at a big concert? Risky.


    NgRx: The Strict Conductor

    NgRx works by defining actions, reducers, and selectors. It’s precise but verbose. Here’s a small example:

    // Actions
    import { createAction, props } from '@ngrx/store';
    
    export const addSong = createAction('[Playlist] Add Song', props<{ song: string }>());
    
    // Reducer
    import { createReducer, on } from '@ngrx/store';
    
    export const playlistReducer = createReducer(
      [],
      on(addSong, (state, { song }) => [...state, song])
    );
    
    // Selector
    import { createSelector } from '@ngrx/store';
    
    export const selectPlaylist = createSelector(
      (state: any) => state.playlist,
      (playlist) => playlist
    );

    With NgRx, everything is explicit. Every “note” (state change) has to be documented as an action, processed in a reducer, and accessed through selectors. It’s powerful for big, complex apps but feels like overkill for smaller projects.


    Akita: The Flexible Band Leader

    Akita lets you work with stores in a simpler and more mutable way. It supports structure while being less restrictive:

    import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
    
    // Store
    export interface PlaylistState extends EntityState<string> {}
    @StoreConfig({ name: 'playlist' })
    export class PlaylistStore extends EntityStore<PlaylistState> {
      constructor() {
        super({});
      }
    }
    
    // Service
    @Injectable({ providedIn: 'root' })
    export class PlaylistService {
      constructor(private playlistStore: PlaylistStore) {}
    
      addSong(song: string) {
        this.playlistStore.add({ id: Date.now(), song });
      }
    }
    
    // Query
    @Injectable({ providedIn: 'root' })
    export class PlaylistQuery extends QueryEntity<PlaylistState> {
      constructor(protected store: PlaylistStore) {
        super(store);
      }
    
      getPlaylist$ = this.selectAll();
    }

    Akita is less verbose than NgRx and allows direct interaction with the store. It balances structure with flexibility, ideal for projects where scalability and developer experience are equally important.


    MobX: The Magic Earpiece

    MobX revolves around reactive state management. It’s simple and dynamic:

    import { makeAutoObservable } from 'mobx';
    
    // Store
    class PlaylistStore {
      playlist = [];
    
      constructor() {
        makeAutoObservable(this);
      }
    
      addSong(song) {
        this.playlist.push(song);
      }
    }
    
    const store = new PlaylistStore();
    
    // React Component
    import { observer } from 'mobx-react-lite';
    
    const Playlist = observer(() => {
      return (
        <div>
          {store.playlist.map((song, index) => (
            <div key={index}>{song}</div>
          ))}
          <button onClick={() => store.addSong('New Song')}>Add Song</button>
        </div>
      );
    });

    MobX makes state reactive, so any change to the store automatically updates the UI. It’s quick and ideal for small projects or when you need rapid prototyping.


    Key Takeaways

    1. NgRx: Best for large-scale apps needing strict, predictable state management. Think orchestras.
    2. Akita: Strikes a balance between structure and flexibility. Great for medium-sized apps. Think jam bands.
    3. MobX: Lightweight, reactive, and simple. Perfect for small projects and fast development. Think solo gigs.

    Each library shines in its context. It all depends on your app’s complexity, team size, and performance needs.