DEV Community

Nigro Simone
Nigro Simone

Posted on • Edited on

NgSimpleState. Simple state management in Angular with only Services and RxJS or Signal.

One of the most challenging things in software development is state management. Currently there are several state management libraries for Angular apps: NGRX, NGXS, Akita...

But what if you don't want to learn, setup, and deal with all the boilerplate for a simple project, what if you want to manage state by only using tools you already know well as an Angular developer.

In this write up, I'll show you a simple way of managing state by only using RxJS and Dependency Injection with NgSimpleState.

Step 1: install ng-simple-state

npm i ng-simple-state
Enter fullscreen mode Exit fullscreen mode

Step 2: Import provideNgSimpleState into your providers

import { isDevMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { provideNgSimpleState } from 'ng-simple-state';

bootstrapApplication(AppComponent, {
  providers: [
    provideNgSimpleState({
      enableDevTool: isDevMode(),
      persistentStorage: 'local'
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Create your store

This is an example for a counter store in a src/app/counter-store.ts file.
Obviously, you can create every store you want with every complexity you need.

1) Define yuor state interface, eg.:

export interface CounterState {
    count: number;
}
Enter fullscreen mode Exit fullscreen mode

2) Define your store service by extending NgSimpleStateBaseRxjsStore or NgSimpleStateBaseSignalStore, eg.:

import { Injectable } from '@angular/core';
import { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseRxjsStore<CounterState> {

}
Enter fullscreen mode Exit fullscreen mode

3) Implement initialState() method and provide the initial state of the store, eg.:

import { Injectable } from '@angular/core';
import { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseRxjsStore<CounterState> {

  initialState(): CounterState {
    return {
      count: 0
    };
  }

}
Enter fullscreen mode Exit fullscreen mode

4) Implement one or more selectors of the partial state you want, in this example selectCount() eg.:

import { Injectable } from '@angular/core';
import { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';
import { Observable } from 'rxjs';

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseRxjsStore<CounterState> {

  initialState(): CounterState {
    return {
      count: 0
    };
  }

  selectCount(): Observable<number> {
    return this.selectState(state => state.count);
  }
}
Enter fullscreen mode Exit fullscreen mode

5) Implement one or more actions for change the store state, in this example increment() and decrement() eg.:

import { Injectable } from '@angular/core';
import { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';
import { Observable } from 'rxjs';

export interface CounterState {
  count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseRxjsStore<CounterState> {

  initialState(): CounterState {
    return {
      count: 0
    };
  }

  selectCount(): Observable<number> {
    return this.selectState(state => state.count);
  }

  increment(increment: number = 1): void {
    this.setState(state => ({ count: state.count + increment }));
  }

  decrement(decrement: number = 1): void {
    this.setState(state => ({ count: state.count - decrement }));
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Use your store into the components, eg.:

import { Component, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { CounterStore } from './counter-store';

@Component({
  selector: 'app-root',
  imports: [CounterStore],
  template: `
  <h1>Counter: {{ counter$ | async }}</h1>
  <button (click)="counterStore.decrement()">Decrement</button>
  <button (click)="counterStore.increment()">Increment</button>
  `,
})
export class AppComponent {
  public counterStore = inject(CounterStore);
  public counter$: Observable<number>;
}
Enter fullscreen mode Exit fullscreen mode

That's all!

alt text

see the live demo.

Top comments (0)