{"id":13808780,"url":"https://github.com/nigrosimone/ng-simple-state","last_synced_at":"2025-04-09T14:06:36.485Z","repository":{"id":48555606,"uuid":"363216871","full_name":"nigrosimone/ng-simple-state","owner":"nigrosimone","description":"Simple state management in Angular with only Services and RxJS or Signal.","archived":false,"fork":false,"pushed_at":"2025-01-05T18:57:55.000Z","size":3107,"stargazers_count":41,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T13:08:19.388Z","etag":null,"topics":["angular","angular2","rxjs","state","state-management"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/ng-simple-state","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nigrosimone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"open_collective":"simone-nigro"}},"created_at":"2021-04-30T17:45:55.000Z","updated_at":"2025-03-21T14:09:52.000Z","dependencies_parsed_at":"2024-01-01T09:00:21.869Z","dependency_job_id":"a36087c5-936b-4177-9f2f-df2b2bc60f3d","html_url":"https://github.com/nigrosimone/ng-simple-state","commit_stats":{"total_commits":266,"total_committers":2,"mean_commits":133.0,"dds":0.003759398496240629,"last_synced_commit":"ff00cd4017cc7548b476561f4456537babb93e69"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nigrosimone%2Fng-simple-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nigrosimone%2Fng-simple-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nigrosimone%2Fng-simple-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nigrosimone%2Fng-simple-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nigrosimone","download_url":"https://codeload.github.com/nigrosimone/ng-simple-state/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248054227,"owners_count":21039952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular2","rxjs","state","state-management"],"created_at":"2024-08-04T01:01:51.729Z","updated_at":"2025-04-09T14:06:36.455Z","avatar_url":"https://github.com/nigrosimone.png","language":"TypeScript","funding_links":["https://opencollective.com/simone-nigro","https://www.paypal.com/paypalme/snwp"],"categories":["State Management"],"sub_categories":["Other State Libraries"],"readme":"# NgSimpleState [![Build Status](https://app.travis-ci.com/nigrosimone/ng-simple-state.svg?branch=main)](https://app.travis-ci.com/nigrosimone/ng-simple-state) [![Coverage Status](https://coveralls.io/repos/github/nigrosimone/ng-simple-state/badge.svg?branch=main)](https://coveralls.io/github/nigrosimone/ng-simple-state?branch=main) [![NPM version](https://img.shields.io/npm/v/ng-simple-state.svg)](https://www.npmjs.com/package/ng-simple-state) [![Maintainability](https://api.codeclimate.com/v1/badges/1bfc363a95053ecc3429/maintainability)](https://codeclimate.com/github/nigrosimone/ng-simple-state/maintainability)\n\nSimple state management in Angular with only Services and RxJS or Signal.\n\n## Description\n\nSharing state between components as simple as possible and leverage the good parts of component state and Angular's dependency injection system.\n\nSee the demos:\n - [Counter](https://stackblitz.com/edit/demo-ng-simple-state?file=src%2Fapp%2Fapp.component.ts)\n - [Tour of heroes](https://stackblitz.com/edit/ng-simple-state-tour-of-heroes?file=src%2Fapp%2Fhero.service.ts)\n - [To Do List](https://stackblitz.com/edit/ng-simple-state-todo?file=src%2Fapp%2Fapp.component.ts)\n\n## Get Started\n\n### Step 1: install `ng-simple-state`\n\n```bash\nnpm i ng-simple-state\n```\n\n### Step 2: Import `provideNgSimpleState` into your providers\n\n`provideNgSimpleState` has some global optional config defined by `NgSimpleStateConfig` interface:\n\n| Option               | Description                                                                                     | Default    |\n| -------------------- | ----------------------------------------------------------------------------------------------- | ---------- |\n| *enableDevTool*      | if `true` enable `Redux DevTools` browser extension for inspect the state of the store.         | `false`    |\n| *persistentStorage*  | Set the persistent storage `local` or `session`.                                                | undefined  |\n| *comparator*         | A function used to compare the previous and current state for equality.                         | `a === b`  |\n\n_Side note: each store can be override the global configuration implementing `storeConfig()` method (see \"Override global config\")._\n\n```ts\nimport { isDevMode } from '@angular/core';\nimport { bootstrapApplication } from '@angular/platform-browser';\n\nimport { AppComponent } from './app.component';\nimport { provideNgSimpleState } from 'ng-simple-state';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideNgSimpleState({\n      enableDevTool: isDevMode(),\n      persistentStorage: 'local'\n    })\n  ]\n});\n```\n\n### Step 3: Chose your store\n\nThere are two type of store `NgSimpleStateBaseRxjsStore` based on RxJS `BehaviorSubject` and `NgSimpleStateBaseSignalStore` based on Angular `Signal`:\n\n- [RxJS Store](#rxjs-store)\n- [Signal Store](#signal-store)\n\n## RxJS Store\n\nThis is an example for a counter store in a `src/app/counter-store.ts` file. \nObviously, you can create every store you want with every complexity you need.\n\n1) Define your state interface, eg.:\n\n```ts\nexport interface CounterState {\n    count: number;\n}\n```\n\n2) Define your store service by extending `NgSimpleStateBaseRxjsStore`, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n \n}\n```\n\n3) Implement `initialState()` and `storeConfig()` methods and provide the initial state of the store, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n  \n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n}\n```\n\n4) Implement one or more selectors of the partial state you want, in this example `selectCount()` eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\nimport { Observable } from 'rxjs';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n  \n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  selectCount(): Observable\u003cnumber\u003e {\n    return this.selectState(state =\u003e state.count);\n  }\n}\n```\n \n5) Implement one or more actions for change the store state, in this example `increment()` and `decrement()` eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\nimport { Observable } from 'rxjs';\n\nexport interface CounterState {\n  count: number;\n}\n\n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n\n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  selectCount(): Observable\u003cnumber\u003e {\n    return this.selectState(state =\u003e state.count);\n  }\n\n  increment(increment: number = 1): void {\n    this.setState(state =\u003e ({ count: state.count + increment }));\n  }\n\n  decrement(decrement: number = 1): void {\n    this.setState(state =\u003e ({ count: state.count - decrement }));\n  }\n}\n```\n\n#### Step 3: Inject your store into the providers, eg.:\n\n```ts\nimport { Component } from '@angular/core';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  selector: 'app-root',\n  imports: [CounterStore]\n})\nexport class AppComponent {\n\n}\n```\n\n#### Step 4: Use your store into the components, eg.:\n\n```ts\nimport { Component, inject } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  selector: 'app-root',\n  imports: [CounterStore],\n  template: `\n  \u003ch1\u003eCounter: {{ counter$ | async }}\u003c/h1\u003e\n  \u003cbutton (click)=\"counterStore.decrement()\"\u003eDecrement\u003c/button\u003e\n  \u003cbutton (click)=\"counterStore.resetState()\"\u003eReset\u003c/button\u003e\n  \u003cbutton (click)=\"counterStore.increment()\"\u003eIncrement\u003c/button\u003e\n  `,\n})\nexport class AppComponent {\n  public counterStore = inject(CounterStore);\n  public counter$: Observable\u003cnumber\u003e = this.counterStore.selectCount();\n}\n```\n\n#### That's all!\n\n![alt text](https://github.com/nigrosimone/ng-simple-state/blob/main/projects/ng-simple-state-demo/src/assets/dev-tool.gif?raw=true)\n\n### Manage component state without service\n\nIf you want manage just a component state without make a new service, your component can extend directly `NgSimpleStateBaseRxjsStore`:\n\n```ts\nimport { Component } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';\nimport { Observable } from 'rxjs';\n\nexport interface CounterState {\n    count: number;\n}\n\n@Component({\n    selector: 'app-counter',\n    template: `\n        {{counter$ | async}}\n        \u003cbutton (click)=\"increment()\"\u003e+\u003c/button\u003e\n        \u003cbutton (click)=\"decrement()\"\u003e-\u003c/button\u003e\n    `\n})\nexport class CounterComponent extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n\n    public counter$: Observable\u003cnumber\u003e = this.selectState(state =\u003e state.count);\n\n    storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n      return {\n        storeName: 'CounterComponent'\n      };\n    }\n\n    initialState(): CounterState {\n        return {\n            count: 0\n        };\n    }\n\n    increment(): void {\n        this.setState(state =\u003e ({ count: state.count + 1 }));\n    }\n\n    decrement(): void {\n        this.setState(state =\u003e ({ count: state.count - 1 }));\n    }\n}\n```\n\n### Override global config\n\nIf you need to override the global configuration provided by `provideNgSimpleState()` you can implement `storeConfig()` and return a specific configuration for the single store, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateStoreConfig } from 'ng-simple-state';\n\n\n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseRxjsStore\u003cCounterState\u003e {\n\n  override storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      persistentStorage: 'session', // persistentStorage can be 'session' or 'local' (default is localStorage)\n      storeName: 'CounterStore2', // set a specific name for this store (must be be unique)\n    }\n  }\n}\n```\n\nThe options are defined by `NgSimpleStateStoreConfig` interface:\n\n| Option               | Description                                                                                     | Default    |\n| -------------------- | ----------------------------------------------------------------------------------------------- | ---------- |\n| *enableDevTool*      | if `true` enable `Redux DevTools` browser extension for inspect the state of the store.         | `false`    |\n| *storeName*          | The store name.                                                                                 | undefined  |\n| *persistentStorage*  | Set the persistent storage `local` or `session`                                                 | undefined  |\n| *comparator*         | A function used to compare the previous and current state for equality.                         | `a === b`  |\n\n\n### Testing\n\n`ng-simple-state` is simple to test. Eg.:\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { provideNgSimpleState } from 'ng-simple-state';\nimport { CounterStore } from './counter-store';\n\ndescribe('CounterStore', () =\u003e {\n\n  let counterStore: CounterStore;\n\n  beforeEach(() =\u003e {\n    TestBed.configureTestingModule({\n      providers: [\n        provideNgSimpleState({\n          enableDevTool: false\n        }),\n        CounterStore\n      ]\n    });\n\n    counterStore = TestBed.inject(CounterStore);\n  });\n\n  it('initialState', () =\u003e {\n    expect(counterStore.getCurrentState()).toEqual({ count: 0 });\n  });\n\n  it('increment', () =\u003e {\n    counterStore.increment();\n    expect(counterStore.getCurrentState()).toEqual({ count: 1 });\n  });\n\n  it('decrement', () =\u003e {\n    counterStore.decrement();\n    expect(counterStore.getCurrentState()).toEqual({ count: -1 });\n  });\n\n  it('selectCount', (done) =\u003e {\n    counterStore.selectCount().subscribe(value =\u003e {\n      expect(value).toBe(0);\n      done();\n    });\n  });\n\n});\n```\n\n### Example: array store\n\nThis is an example for a todo list store in a `src/app/todo-store.ts` file. \n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseRxjsStore } from 'ng-simple-state';\nimport { Observable } from 'rxjs';\n\nexport interface Todo {\n  id: number;\n  name: string;\n  completed: boolean;\n}\n\nexport type TodoState = Array\u003cTodo\u003e;\n\n@Injectable()\nexport class TodoStore extends NgSimpleStateBaseRxjsStore\u003cTodoState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'TodoStore'\n    };\n  }\n\n  initialState(): TodoState {\n    return [];\n  }\n\n  add(todo: Omit\u003cTodo, 'id'\u003e): void {\n    this.setState(state =\u003e  [...state, {...todo, id: Date.now()}]);\n  }\n\n  delete(id: number): void {\n    this.setState(state =\u003e state.filter(item =\u003e item.id !== id) );\n  }\n\n  setComplete(id: number, completed: boolean = true): void {\n    this.setState(state =\u003e state.map(item =\u003e item.id === id ? {...item, completed} : item) );\n  }\n}\n```\n\nusage:\n\n```ts\nimport { Component, inject } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Todo, TodoStore } from './todo-store';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    \u003cinput #newTodo\u003e \u003cbutton (click)=\"todoStore.add({name: newTodo.value, completed: false})\"\u003eAdd todo\u003c/button\u003e\n    \u003col\u003e\n      @for(todo of todoList$ | async; track todo.id) {\n        \u003cli\u003e\n            @if(todo.completed) {\n              ✅\n            } \n            {{ todo.name }} \n            \u003cbutton (click)=\"todoStore.setComplete(todo.id, !todo.completed)\"\u003eMark as {{ todo.completed ? 'Not completed' : 'Completed' }}\u003c/button\u003e \n            \u003cbutton (click)=\"todoStore.delete(todo.id)\"\u003eDelete\u003c/button\u003e\n        \u003c/li\u003e\n      }\n    \u003c/ol\u003e\n  `,\n  providers: [TodoStore]\n})\nexport class AppComponent {\n  public todoStore = inject(TodoStore);\n  public todoList$: Observable\u003cTodo[]\u003e = this.todoStore.selectState();\n}\n```\n\n\n### NgSimpleStateBaseRxjsStore API\n\n```ts\n@Injectable()\n@Directive()\nexport abstract class NgSimpleStateBaseRxjsStore\u003cS extends object | Array\u003cany\u003e\u003e implements OnDestroy {\n\n    /**\n     * Return the observable of the state\n     * @returns Observable of the state\n     */\n    public get state(): BehaviorSubject\u003cS\u003e;\n\n    /**\n     * When you override this method, you have to call the `super.ngOnDestroy()` method in your `ngOnDestroy()` method.\n     */\n    ngOnDestroy(): void;\n\n    /**\n     * Reset store to first loaded store state:\n     *  - the last saved state\n     *  - otherwise the initial state provided from `initialState()` method.\n     */\n    resetState(): boolean;\n\n    /**\n     * Restart the store to initial state provided from `initialState()` method\n     */\n    restartState(): boolean;\n\n    /**\n     * Override this method for set a specific config for the store\n     * @returns NgSimpleStateStoreConfig\n     */\n    storeConfig(): NgSimpleStateStoreConfig\u003cS\u003e;\n\n    /**\n     * Set into the store the initial state\n     * @returns The state object\n     */\n    initialState(): S;\n\n    /**\n     * Select a store state\n     * @param selectFn State selector (if not provided return full state)\n     * @param comparator A function used to compare the previous and current state for equality. Defaults to a `===` check.\n     * @returns Observable of the selected state\n     */\n    selectState\u003cK\u003e(selectFn?: (state: Readonly\u003cS\u003e) =\u003e K, comparator?: (previous: K, current: K) =\u003e boolean): Observable\u003cK\u003e;\n\n    /**\n     * Return the current store state (snapshot)\n     * @returns The current state\n     */\n    getCurrentState(): Readonly\u003cS\u003e;\n\n    /**\n     * Return the first loaded store state:\n     * the last saved state\n     * otherwise the initial state provided from `initialState()` method.\n     * @returns The first state\n     */\n    getFirstState(): Readonly\u003cS\u003e | null;\n\n    /**\n     * Set a new state\n     * @param selectFn State reducer\n     * @param actionName The action label into Redux DevTools (default is parent function name)\n     * @returns True if the state is changed\n     */\n    setState(stateFn: (currentState: Readonly\u003cS\u003e) =\u003e Partial\u003cS\u003e, actionName?: string): boolean; \n}\n```\n## Signal Store\n\nThis is an example for a counter store in a `src/app/counter-store.ts` file. \nObviously, you can create every store you want with every complexity you need.\n\n1) Define your state interface, eg.:\n\n```ts\nexport interface CounterState {\n    count: number;\n}\n```\n\n2) Define your store service by extending `NgSimpleStateBaseSignalStore`, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n \n}\n```\n\n3) Implement `initialState()` and `storeConfig()` methods and provide the initial state of the store, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n  \n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n}\n```\n\n4) Implement one or more selectors of the partial state you want, in this example `selectCount()` eg.:\n\n```ts\nimport { Injectable, Signal } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n \n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n  \n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  selectCount(): Signal\u003cnumber\u003e {\n    return this.selectState(state =\u003e state.count);\n  }\n}\n```\n \n5) Implement one or more actions for change the store state, in this example `increment()` and `decrement()` eg.:\n\n```ts\nimport { Injectable, Signal } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore, NgSimpleStateStoreConfig } from 'ng-simple-state';\n\nexport interface CounterState {\n  count: number;\n}\n\n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'CounterStore'\n    };\n  }\n\n  initialState(): CounterState {\n    return {\n      count: 0\n    };\n  }\n\n  selectCount(): Signal\u003cnumber\u003e {\n    return this.selectState(state =\u003e state.count);\n  }\n\n  increment(increment: number = 1): void {\n    this.setState(state =\u003e ({ count: state.count + increment }));\n  }\n\n  decrement(decrement: number = 1): void {\n    this.setState(state =\u003e ({ count: state.count - decrement }));\n  }\n}\n```\n\n#### Step 3: Inject your store into the providers, eg.:\n\n```ts\nimport { Component } from '@angular/core';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  selector: 'app-root',\n  imports: [CounterStore]\n})\nexport class AppComponent {\n\n}\n```\n\n#### Step 4: Use your store into the components, eg.:\n\n```ts\nimport { Component, Signal, inject } from '@angular/core';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  selector: 'app-root',\n  template: `\n  \u003ch1\u003eCounter: {{ counterSig() }}\u003c/h1\u003e\n  \u003cbutton (click)=\"counterStore.decrement()\"\u003eDecrement\u003c/button\u003e\n  \u003cbutton (click)=\"counterStore.resetState()\"\u003eReset\u003c/button\u003e\n  \u003cbutton (click)=\"counterStore.increment()\"\u003eIncrement\u003c/button\u003e\n  `,\n})\nexport class AppComponent {\n  public counterStore = inject(CounterStore);\n  public counterSig: Signal\u003cnumber\u003e = this.counterStore.selectCount();\n}\n```\n\n#### That's all!\n\n![alt text](https://github.com/nigrosimone/ng-simple-state/blob/main/projects/ng-simple-state-demo/src/assets/dev-tool.gif?raw=true)\n\n### Manage component state without service\n\nIf you want manage just a component state without make a new service, your component can extend directly `NgSimpleStateBaseSignalStore`:\n\n```ts\nimport { Component, Signal } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore } from 'ng-simple-state';\n\nexport interface CounterState {\n    count: number;\n}\n\n@Component({\n    selector: 'app-counter',\n    template: `\n        {{counterSig()}}\n        \u003cbutton (click)=\"increment()\"\u003e+\u003c/button\u003e\n        \u003cbutton (click)=\"decrement()\"\u003e-\u003c/button\u003e\n    `\n})\nexport class CounterComponent extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n\n    public counterSig: Signal\u003cnumber\u003e = this.selectState(state =\u003e state.count);\n\n    storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n      return {\n        storeName: 'CounterComponent'\n      };\n    }\n\n    initialState(): CounterState {\n        return {\n            count: 0\n        };\n    }\n\n    increment(): void {\n        this.setState(state =\u003e ({ count: state.count + 1 }));\n    }\n\n    decrement(): void {\n        this.setState(state =\u003e ({ count: state.count - 1 }));\n    }\n}\n```\n\n### Override global config\n\nIf you need to override the global configuration provided by `provideNgSimpleState()` you can implement `storeConfig()` and return a specific configuration for the single store, eg.:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateStoreConfig } from 'ng-simple-state';\n\n\n@Injectable()\nexport class CounterStore extends NgSimpleStateBaseSignalStore\u003cCounterState\u003e {\n\n  override storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      persistentStorage: 'session', // persistentStorage can be 'session' or 'local' (default is localStorage)\n      storeName: 'CounterStore2', // set a specific name for this store (must be be unique)\n    }\n  }\n}\n```\n\nThe options are defined by `NgSimpleStateStoreConfig` interface:\n\n| Option               | Description                                                                                     | Default    |\n| -------------------- | ----------------------------------------------------------------------------------------------- | ---------- |\n| *enableDevTool*      | if `true` enable `Redux DevTools` browser extension for inspect the state of the store.         | `false`    |\n| *storeName*          | The store name.                                                                                 | undefined  |\n| *persistentStorage*  | Set the persistent storage `local` or `session`                                                 | undefined  |\n| *comparator*         | A function used to compare the previous and current state for equality.                         | `a === b`  |\n\n### Testing\n\n`ng-simple-state` is simple to test. Eg.:\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { provideNgSimpleState } from 'ng-simple-state';\nimport { CounterStore } from './counter-store';\n\ndescribe('CounterStore', () =\u003e {\n\n  let counterStore: CounterStore;\n\n  beforeEach(() =\u003e {\n    TestBed.configureTestingModule({\n      providers: [\n        provideNgSimpleState({\n          enableDevTool: false\n        }),\n        CounterStore\n      ]\n    });\n\n    counterStore = TestBed.inject(CounterStore);\n  });\n\n  it('initialState', () =\u003e {\n    expect(counterStore.getCurrentState()).toEqual({ count: 0 });\n  });\n\n  it('increment', () =\u003e {\n    counterStore.increment();\n    expect(counterStore.getCurrentState()).toEqual({ count: 1 });\n  });\n\n  it('decrement', () =\u003e {\n    counterStore.decrement();\n    expect(counterStore.getCurrentState()).toEqual({ count: -1 });\n  });\n\n  it('selectCount', () =\u003e {\n    const valueSig = counterStore.selectCount();\n    expect(valueSig()).toBe(0);\n  });\n\n});\n```\n\n### Example: array store\n\nThis is an example for a todo list store in a `src/app/todo-store.ts` file. \n\n```ts\nimport { Injectable } from '@angular/core';\nimport { NgSimpleStateBaseSignalStore } from 'ng-simple-state';\n\nexport interface Todo {\n  id: number;\n  name: string;\n  completed: boolean;\n}\n\nexport type TodoState = Array\u003cTodo\u003e;\n\n@Injectable()\nexport class TodoStore extends NgSimpleStateBaseSignalStore\u003cTodoState\u003e {\n\n  storeConfig(): NgSimpleStateStoreConfig\u003cCounterState\u003e {\n    return {\n      storeName: 'TodoStore'\n    };\n  }\n\n  initialState(): TodoState {\n    return [];\n  }\n\n  add(todo: Omit\u003cTodo, 'id'\u003e): void {\n    this.setState(state =\u003e  [...state, {...todo, id: Date.now()}]);\n  }\n\n  delete(id: number): void {\n    this.setState(state =\u003e state.filter(item =\u003e item.id !== id) );\n  }\n\n  setComplete(id: number, completed: boolean = true): void {\n    this.setState(state =\u003e state.map(item =\u003e item.id === id ? {...item, completed} : item) );\n  }\n}\n```\n\nusage:\n\n```ts\nimport { Component, Signal, inject } from '@angular/core';\nimport { Todo, TodoStore } from './todo-store';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    \u003cinput #newTodo\u003e \u003cbutton (click)=\"todoStore.add({name: newTodo.value, completed: false})\"\u003eAdd todo\u003c/button\u003e\n    \u003col\u003e\n      @for(todo of todoListSig() | async; track todo.id) {\n        \u003cli\u003e\n            @if(todo.completed) {\n              ✅\n            }\n            {{ todo.name }} \n            \u003cbutton (click)=\"todoStore.setComplete(todo.id, !todo.completed)\"\u003eMark as {{ todo.completed ? 'Not completed' : 'Completed' }}\u003c/button\u003e \n            \u003cbutton (click)=\"todoStore.delete(todo.id)\"\u003eDelete\u003c/button\u003e\n        \u003c/li\u003e\n      }\n    \u003c/ol\u003e\n  `,\n  providers: [TodoStore]\n})\nexport class AppComponent {\n  public todoStore = inject(TodoStore);\n  public todoListSig: Signal\u003cTodo[]\u003e = this.todoStore.selectState();\n}\n```\n\n\n### NgSimpleStateBaseSignalStore API\n\n```ts\n@Injectable()\n@Directive()\nexport abstract class NgSimpleStateBaseSignalStore\u003cS extends object | Array\u003cany\u003e\u003e implements OnDestroy {\n\n    /**\n     * Return the Signal of the state\n     * @returns Signal of the state\n     */\n    public get state(): Signal\u003cS\u003e;\n\n    /**\n     * When you override this method, you have to call the `super.ngOnDestroy()` method in your `ngOnDestroy()` method.\n     */\n    ngOnDestroy(): void;\n\n    /**\n     * Reset store to first loaded store state:\n     *  - the last saved state\n     *  - otherwise the initial state provided from `initialState()` method.\n     */\n    resetState(): boolean;\n\n    /**\n     * Restart the store to initial state provided from `initialState()` method\n     */\n    restartState(): boolean;\n\n    /**\n     * Override this method for set a specific config for the store\n     * @returns NgSimpleStateStoreConfig\n     */\n    storeConfig(): NgSimpleStateStoreConfig\u003cS\u003e;\n\n    /**\n     * Set into the store the initial state\n     * @returns The state object\n     */\n    initialState(): S;\n\n    /**\n     * Select a store state\n     * @param selectFn State selector (if not provided return full state)\n     * @param comparator A function used to compare the previous and current state for equality. Defaults to a `===` check.\n     * @returns Signal of the selected state\n     */\n    selectState\u003cK\u003e(selectFn?: (state: Readonly\u003cS\u003e) =\u003e K, comparator?: (previous: K, current: K) =\u003e boolean): Signal\u003cK\u003e;\n\n    /**\n     * Return the current store state (snapshot)\n     * @returns The current state\n     */\n    getCurrentState(): Readonly\u003cS\u003e;\n\n    /**\n     * Return the first loaded store state:\n     * the last saved state\n     * otherwise the initial state provided from `initialState()` method.\n     * @returns The first state\n     */\n    getFirstState(): Readonly\u003cS\u003e | null;\n\n    /**\n     * Set a new state\n     * @param selectFn State reducer\n     * @param actionName The action label into Redux DevTools (default is parent function name)\n     * @returns True if the state is changed\n     */\n    setState(stateFn: (currentState: Readonly\u003cS\u003e) =\u003e Partial\u003cS\u003e, actionName?: string): boolean; \n}\n```\n\n## Alternatives\n\nAren't you satisfied? there are some valid alternatives:\n\n - [@tinystate](https://www.npmjs.com/package/@tinystate/core)\n - [@ngxs](https://www.npmjs.com/package/@ngxs/store)\n## Support\n\nThis is an open-source project. Star this [repository](https://github.com/nigrosimone/ng-simple-state), if you like it, or even [donate](https://www.paypal.com/paypalme/snwp). Thank you so much!\n\n## My other libraries\n\nI have published some other Angular libraries, take a look:\n\n - [NgHttpCaching: Cache for HTTP requests in Angular application](https://www.npmjs.com/package/ng-http-caching)\n - [NgGenericPipe: Generic pipe for Angular application for use a component method into component template.](https://www.npmjs.com/package/ng-generic-pipe)\n - [NgLet: Structural directive for sharing data as local variable into html component template](https://www.npmjs.com/package/ng-let)\n - [NgForTrackByProperty: Angular global trackBy property directive with strict type checking](https://www.npmjs.com/package/ng-for-track-by-property)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnigrosimone%2Fng-simple-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnigrosimone%2Fng-simple-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnigrosimone%2Fng-simple-state/lists"}