DEV Community

Nigro Simone
Nigro Simone

Posted on

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

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 NgSimpleStateModule into your AppModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CommonModule } from '@angular/common';
import { NgSimpleStateModule } from 'ng-simple-state';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    CommonModule,
    NgSimpleStateModule.forRoot({
      enableDevTool: !environment.production, // Enable Redux DevTools only in developing
    }) 
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
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 NgSimpleStateBaseStore, eg.:

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

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseStore<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 { NgSimpleStateBaseStore } from 'ng-simple-state';

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseStore<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 { NgSimpleStateBaseStore } from 'ng-simple-state';
import { Observable } from 'rxjs';

export interface CounterState {
    count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseStore<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 { NgSimpleStateBaseStore } from 'ng-simple-state';
import { Observable } from 'rxjs';

export interface CounterState {
  count: number;
}

@Injectable()
export class CounterStore extends NgSimpleStateBaseStore<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: Inject your store into the providers of the module you want (or the providers of component), eg.:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CommonModule } from '@angular/common';
import { NgSimpleStateModule } from 'ng-simple-state';
import { environment } from '../environments/environment';
import { CounterStore } from './counter-store';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    CommonModule,
    NgSimpleStateModule.forRoot({
      enableDevTool: !environment.production, // Enable Redux DevTools only in developing
      enableLocalStorage: false // Local storage state persistence is globally disabled
    })
  ],
  bootstrap: [AppComponent],
  providers: [CounterStore]  // The CounterStore state is shared at AppModule level
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

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

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

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

  constructor(public counterStore: CounterStore) {
    this.counter$ = this.counterStore.selectCount();
  }
}
Enter fullscreen mode Exit fullscreen mode

That's all!

alt text

see the live demo.

Top comments (0)