STATE-STORE

Sample application for the Ngx-State-Store module usage features demonstration.


Introduction


ngx-state-store is based on RxJS, easy to learn and use, light and quick the state management module for the Angular applications starting from the Angular version >= 7.2.0

state-store is the sample Angular application for the ngx-state-store module usage features demonstration.

ngx-state-store and state-store were updated and tested up to the Angular version 17

This documentation is to help you regarding the ngx-state-store module. The presented simplified code examples are to help you to grasp the major idea behind the module.

The benefits of using the ngx-state-store in comparison to other known state management frameworks are: much less written code, better overview over the code, subscription to async actions, simple to refactor by new implementation of an action.

Requirements

It is expected to have the following knowledge:

  1. TypeScript is an open-source language which builds on JavaScript, one of the world’s most used tools, by adding static type definitions.
  2. Angular is a platform and framework for building single-page client applications using HTML and TypeScript.
  3. RxJS is the library for composing asynchronous and event-based programs by using observable sequences.
  4. Understanding of the necessity of the state management in the modern web applications.
  5. Some modern web development platform like for example IntelliJ WebStorm or Microsoft Visual Studio Code for the TypeScript/Angular development.

Simple example

Create a state object

Source: src/app/services/state-store/app-state.ts
              export interface AppState {
                Counter: number;
              }
              

Create an initial state object

Source: src/app/services/state-store/app-initial-state.ts
              import { AppState } from './app-state';

              export const AppInitialState: AppState = {
                Counter: 0
              };
              

Register the module

Source: src/app/app.module.ts
              // ...
              import { NgModule } from '@angular/core';
              import { AppInitialState } from './services/state-store/app-initial-state';
              import { NgxStateStoreModule } from 'ngx-state-store';
              // ...

              @NgModule({
                declarations: [
                  // ...
                ],
                imports: [
                  // ...
                  NgxStateStoreModule.forRoot({
                    storeName: 'store-demo',
                    log: true,
                    timekeeping: true,
                    initialState: AppInitialState
                  }),
                  // ...
                ],
                // ...
              })
              export class AppModule {
              // ...
              }
              
At run time you can access the store in the console by the path window['ngx-state-store']['store-demo'].
To activate/deactivate log and timekeeping at runtime:
              window['ngx-state-store']['store-demo'].state.log.log = true;
              window['ngx-state-store']['store-demo'].state.performance.timekeeping = true;
              

Create an action

Source: src/app/services/state-store/actions/increment-counter.action.ts
              import { Action, StateContext } from 'ngx-state-store';
              import { ActionIds } from '../action-ids';
              import { AppState } from '../app-state';

              export class IncrementCounterAction extends Action {
                constructor() {
                  super(ActionIds.UpdateCounter);
                }

                handleState(stateContext: StateContext<AppState>): void {
                  const newValue = this.getEmptyState();
                  newValue.Counter = stateContext.getState().Counter + 1;
                  stateContext.patchState(newValue);
                }
              }
              
Source: src/app/services/state-store/action-ids.ts
              export enum ActionIds {
                UpdateCounter = '[Common] update counter'
              }
              
The ids, like one above - [Common] update counter, will be displayed in the console log output.

Create an action factory

Source: src/app/services/state-store/action-factory.ts
              import { Injectable } from '@angular/core';
              import { Action } from 'ngx-state-store';
              import { IncrementCounterAction } from './actions/increment-counter.action';

              @Injectable()
              export class ActionFactory {
                incrementCounter(): Action {
                  return new IncrementCounterAction();
                }
              }
              

Update some state

To update some state by an Action use store.dispatch(...).
Source: src/app/components/counter-button.component/counter-button.component.ts
              import { Component } from '@angular/core';
              import { AppState } from '../../services/state-store/app-state';
              import { ActionFactory } from '../../services/state-store/action-factory';
              import { Store } from 'ngx-state-store';

              @Component({
                selector: 'app-counter-button',
                templateUrl: './counter-button.component.html',
                styleUrls: ['./counter-button.component.scss']
              })
              export class CounterButtonComponent {
                constructor(private store: Store<AppState>,
                  private factory: ActionFactory) {
                }

                incrementCounter() {
                  this.store.dispatch(this.factory.incrementCounter());
                }
              }
              

Subscribe to some state

To subscribe to some state use store.select(...).
Source: src/app/components/counter.component/counter.component.ts
              import { Component, OnInit } from '@angular/core';
              import { Store } from 'ngx-state-store';
              import { AppState } from '../../services/state-store/app-state';
              import { Observable } from 'rxjs';

              @Component({
                selector: 'app-counter',
                templateUrl: './counter.component.html',
                styleUrls: ['./counter.component.scss']
              })
              export class CounterComponent implements OnInit {
                counter$: Observable<number>;

                constructor(private store: Store<AppState>) {
                }

                ngOnInit(): void {
                  this.counter$ = this.store.select('Counter');
                }
              }
              
The select(...) method returns an Observable.



It is a bad design / architecture to put the state management inside of the components as the components must be / are expected to be reusable. The better approach is to put the state management inside of so called container-components. Container-components are the components, that contain all of the reusable components.

The design and architecture are out of the scope of this documentation. The presented examples are simplified for the sake of the clarity.

More complex use case

The more complex use case includes a back-end call.

Extend the state object

Source: src/app/services/state-store/app-state.ts
              import { Inventory } from '../../models/inventory';

              export interface AppState {
                ShowLoadingIndicator: string[];
                Counter: number;
                Inventories: Inventory[];
                // iso Date string
                LastDownloadAt: string;
              }
              
Source: src/app/models/inventory.ts
              export class Inventory {
                id: number;
                version: string;
                name: string;
              }
              

Extend the initial state object

Source: src/app/services/state-store/app-initial-state.ts
              import { AppState } from './app-state';

              export const AppInitialState: AppState = {
                ShowLoadingIndicator: [],
                Counter: 0,
                Inventories: null,
                LastDownloadAt: ''
              };
              

Create new actions

Source: src/app/services/state-store/actions/show-loading-indicator.action.ts
              import { Action, StateContext } from 'ngx-state-store';
              import { ActionIds } from '../action-ids';
              import { AppState } from '../app-state';

              export class ShowLoadingIndicatorAction extends Action {
                constructor(private identifier: string) {
                  super(ActionIds.ShowLoadingIndicator + ': ' + identifier);
                }

                handleState(stateContext: StateContext<AppState>): void {
                  const newState = this.getEmptyState();
                  newState.ShowLoadingIndicator
                    = stateContext.getState().ShowLoadingIndicator.slice();
                  newState.ShowLoadingIndicator.push(this.identifier);
                  stateContext.patchState(newState);
                }
              }
              
Source: src/app/services/state-store/actions/hide-loading-indicator.action.ts
              import { Action, StateContext } from 'ngx-state-store';
              import { ActionIds } from '../action-ids';
              import { AppState } from '../app-state';

              export class HideLoadingIndicatorAction extends Action {
                constructor(private identifier: string) {
                  super(ActionIds.HideLoadingIndicator + ': ' + identifier);
                }

                handleState(stateContext: StateContext<AppState>): void {
                  if (stateContext.getState().ShowLoadingIndicator == null) {
                    return;
                  }

                  const index = stateContext.getState()
                    .ShowLoadingIndicator.indexOf(this.identifier);
                  if (index < 0) {
                    return;
                  }

                  const newState: AppState = this.getEmptyState();
                  newState.ShowLoadingIndicator
                    = stateContext.getState().ShowLoadingIndicator.slice();
                  newState.ShowLoadingIndicator.splice(index, 1);
                  stateContext.patchState(newState);
                }
              }
              
Source: src/app/services/state-store/actions/load-inventories.action.ts
              import { Action, StateContext } from 'ngx-state-store';
              import { ActionIds } from '../action-ids';
              import { AppState } from '../app-state';
              import { InventoryConnector } from '../../connectors/inventory.connector';
              import { Observable } from 'rxjs';
              import { tap } from 'rxjs/operators';

              export class LoadInventoriesAction extends Action {
                constructor(private inventoryConnector: InventoryConnector) {
                  super(ActionIds.LoadInventories);
                }

                handleState(stateContext: StateContext<AppState>): Observable<any> {
                  return this.inventoryConnector.loadInventory()
                    .pipe(
                      tap(inventories => {
                        const newState: AppState = this.getEmptyState();
                        newState.Inventories = inventories;
                        newState.LastDownloadAt = (new Date()).toISOString();
                        stateContext.patchState(newState);
                      })
                  );
                }
              }
              

Extend the action factory

Source: src/app/services/state-store/action-factory.ts
              import { Injectable } from '@angular/core';
              import { Action } from 'ngx-state-store';
              import { ShowLoadingIndicatorAction } from './actions/show-loading-indicator.action';
              import { IncrementCounterAction } from './actions/increment-counter.action';
              import { HideLoadingIndicatorAction } from './actions/hide-loading-indicator.action';
              import { LoadInventoriesAction } from './actions/load-inventories.action';
              import { InventoryConnector } from '../connectors/inventory.connector';

              export enum LoadIndicator {
                DEFAULT = 'DEFAULT',
                LOAD_INVENTORIES = 'LOAD_INVENTORIES'
              }

              @Injectable()
              export class ActionFactory {
                constructor(private inventoryConnector: InventoryConnector) {
                }

                incrementCounter(): Action {
                  return new IncrementCounterAction();
                }

                showLoadIndicator(identifier: string = LoadIndicator.DEFAULT): Action {
                  return new ShowLoadingIndicatorAction(identifier);
                }

                hideLoadIndicator(identifier: string = LoadIndicator.DEFAULT): Action {
                  return new HideLoadingIndicatorAction(identifier);
                }

                loadInventories(): Action {
                  return new LoadInventoriesAction(this.inventoryConnector);
                }
              }
              
Source: src/app/services/connectors/inventory.connector.ts
              import { Injectable } from '@angular/core';
              import { Observable } from 'rxjs';
              import { HttpClient } from '@angular/common/http';
              import { Inventory } from '../../models/inventory';
              import { delay } from 'rxjs/operators';

              @Injectable()
              export class InventoryConnector {
                constructor(private http: HttpClient) {
                }

                loadInventory(): Observable<Inventory[]> {
                  // delay(2000) to imitate the network throttling
                  return this.http.get<Inventory[]>('assets/mock-data/inventories.json')
                    .pipe(delay(2000));
                }
              }
              

Load the data

To load the data from the back-end and update the states by the Actions use store.dispatch(...)
Source: src/app/components/inventories-button.component/inventories-button.component.ts
              import { Component, OnInit } from '@angular/core';
              import { Store } from 'ngx-state-store';
              import { AppState } from '../../services/state-store/app-state';
              import { ActionFactory, LoadIndicator } from '../../services/state-store/action-factory';
              import { catchError, mergeMap, skip } from 'rxjs/operators';
              import { of } from 'rxjs';
              import { Inventory } from '../../models/inventory';

              export interface Changes {
                addedEntries: Inventory[];
                removedEntries: Inventory[];
              }

              @Component({
                selector: 'app-inventories-button',
                templateUrl: './inventories-button.component.html',
                styleUrls: ['./inventories-button.component.scss']
              })
              export class InventoriesButtonComponent implements OnInit {
                lastDownloadAt: string;
                changes: Changes = {addedEntries: [], removedEntries: []} as Changes;
                private inventories: Inventory[] = [];

                constructor(private store: Store<AppState>,
                  private factory: ActionFactory) {
                }

                ngOnInit(): void {
                  this.store.select('Inventories',
                    (oldInventories: Inventory[], newInventories: Inventory[]) => {
                    if (oldInventories === newInventories
                      || oldInventories && newInventories
                      && !this.calcDiff(oldInventories, newInventories).length
                      && !this.calcDiff(newInventories, oldInventories).length) {
                      return true;
                    }
                    return false;
                  }).pipe(skip(1))
                  .subscribe((newInventories) => {
                    this.changes.addedEntries = this.calcDiff(this.inventories, newInventories);
                    this.changes.removedEntries = this.calcDiff(newInventories, this.inventories);
                    this.inventories = newInventories;
                    console.log('the log is present only if there are some changes');
                  });
                }

                private calcDiff(source: Inventory[], target: Inventory[]): Inventory[] {
                  return (target || []).filter(t => !(source || []).find(s => s.id === t.id));
                }

                loadInventory() {
                  this.changes.addedEntries = [];
                  this.changes.removedEntries = [];
                  this.store.dispatch(
                    this.factory.showLoadIndicator(LoadIndicator.LOAD_INVENTORIES))
                  .pipe(
                    mergeMap(() => this.store.dispatch(this.factory.loadInventories())),
                    catchError(error => {
                      console.log(error);
                      return of(error);
                    })
                  ).subscribe((state: AppState) => {
                      this.lastDownloadAt = state.LastDownloadAt;
                      this.store.dispatch(this.factory.hideLoadIndicator(LoadIndicator.LOAD_INVENTORIES));
                    }
                  );
                }

                inventoriesToString(inventories: Inventory[]): string {
                  return inventories.map(e => e.id).toString();
                }
              }
              

Subscribe to the new states

To subscribe to the new states use store.select(...)
Source: src/app/components/inventories.component/inventories.component.ts
              import { Component, OnInit } from '@angular/core';
              import { Store } from 'ngx-state-store';
              import { AppState } from '../../services/state-store/app-state';
              import { Inventory } from '../../models/inventory';
              import { Observable, of } from 'rxjs';
              import { mergeMap } from 'rxjs/operators';
              import { LoadIndicator } from '../../services/state-store/action-factory';

              @Component({
                selector: 'app-inventories',
                templateUrl: './inventories.component.html',
                styleUrls: ['./inventories.component.scss']
              })
              export class InventoriesComponent implements OnInit {
                inventories: Inventory[];
                loading$: Observable<boolean>;

                constructor(private store: Store<AppState>) {
                }

                ngOnInit(): void {
                  this.loading$ = this.store.select('ShowLoadingIndicator').pipe(
                    map(indicators =>
                      indicators.filter(i => i === LoadIndicator.LOAD_INVENTORIES).length > 0)
                  );
                  this.store.select('Inventories').subscribe(inventories => {
                    this.inventories = inventories;
                  });
                }
              }
              
The observables returned from the store.select(...) return a frozen (read only) state objects that were frozen by the StateHelper.deepFreeze(any). Use StateHelper.cloneObject(any, cloneFunctions = true) to get a clone of the frozen object including the functions (default) if it is needed. Cloning the functions is in experimental stage. Usually the state objects do not have any functions, and it is not recommended they have any.



Keep in mind that all objects passed to the state store will be frozen. The freeze process is based on JavaScript Object.freeze()


API overview

Store

  • select - select(string, ObjectComparator?): Observable<any> - select any state of the state store by the first parameter - the state key, the second parameter ObjectComparator is optional. The ObjectComparator can be used to implement a more advanced comparison of potential changes. It is also a convenient way to only subscribe to some property/properties changes of any state object.
  • selectOrDefault - selectOrDefault(string, any, ObjectComparator?): Observable<any> - select any state of the state store by the first parameter - the state key or return the default value, the second parameter, if the state is undefined or null. The third parameter ObjectComparator is optional.
  • selectSubProperty - selectSubProperty(string, string, ObjectComparator?): Observable<any> - select any state sub-property of the state store by the first parameter - the state key. The second parameter sub-property path is a string representation of the property path. For example: if any state identified by 'key1' has a property object with the name 'prop1', and this object has an array of other objects saved in property 'prop2', and the property 'prop3' of the second element (index 1) of the object from the array is needed, then the sub-property path would be: 'prop1/prop2/1/prop3'. The third parameter ObjectComparator is optional.
  • selectSubPropertyOrDefault - select(string, string, any, ObjectComparator?): Observable<any> - select any state sub-property of the state store by the first parameter - the state key or return the default value for the sub-property, the third parameter, if the sub-property is undefined or null. The second parameter is the sub-property path, explanation above. The fourth parameter ObjectComparator is optional.
  • selectOnce - selectOnce(string): Observable<any> - the same as select but the Observable terminates after forward one value.
  • selectOnceOrDefault - selectOnceOrDefault(string, any): Observable<any> - the same as selectOrDefault but the Observable terminates after forward one value.
  • selectOnceSubProperty - selectOnceSubProperty(string, string): Observable<any> - the same as selectSubProperty but the Observable terminates after forward one value.
  • selectOnceSubPropertyOrDefault - selectOnceSubPropertyOrDefault(string, string, any): Observable<any> - the same as selectSubPropertyOrDefault but the Observable terminates after forward one value.
  • dispatch - dispatch(action: Action): Observable<state: S> - dispatch the Action that changes some state, the dispatch function always returns an Observable of your newest state.

Tip: You could join many dispatch calls in a pipe and have access to the last state change from the previous call. In this case, the preferable way to do any changes of the state is by returning an Observable. The Observable itself returning value does not have any meaning. For the return value inside of the Observable you could use return of(null); from rxjs, or operator function tap((x: T) => void).
see the examples: inventories-button.component.ts and load-inventories.action.ts
in the example load-inventories.action.ts the access to the <state: S> is in the subscription.


Pseudo code example:
                  this.store.dispatch(<some action>)
                  .pipe(
                    mergeMap((state: S) => {
                      // ... read and use data from the state
                      return this.store.dispatch(<another action>);
                    }),
                    mergeMap((state: S) => {
                      // ... read and use data from the state
                      return this.store.dispatch(<another action 2>);
                    })
                  ).subscribe((state: S) => {
                    // ... read and use data from the state
                  });
                  

dispatch rules:
1) Do not use store.select(string) in the subscription / pipe to store.dispatch(<some action>).
The store.dispatch(<some action>) returns an Observable that emits only one value, that is newest state, and terminates, whereas the store.select(string) returns an Observable that emits the value every time the state was changed. As the result the subscription stays further active.
2) The store.selectOnce(string) returns stale state if it is placed in the subscription / pipe to store.dispatch(<some action>). This applies, when the dispatching action changes the state.
Only the state emitted by the Observable returned by the store.dispatch(<some action>) and the state of the StateContext in the action.handleState(stateContext) are the newest in the subscription / pipe to store.dispatch(<some action>).

Code snippet that demonstrates the store.dispatch(<some action>) rules
Source: projects/ngx-state-store/src/lib/state/store.service.spec.ts
                  describe('dispatch, pipe and selectOnce', () => {
                    let store: Store<any>;
                    beforeEach(() => {
                      TestBed.configureTestingModule({
                        providers: [Store, {
                            provide: STATE_CONFIG,
                            useValue: {initialState: {Counter: 0}}  // initial Counter = 0
                        }]
                      });
                      store = TestBed.inject(Store);
                    });

                    it('selecting stale state by subscribe to dispatch', (done) => {
                      of(null)
                        .pipe(
                          mergeMap(() => store.dispatch({
                            handleState(stateContext: StateContext<any>): Observable<void> {

                              expect(stateContext.getState().Counter).toBe(0); // initial - Counter = 0

                              return of(null)
                                .pipe(
                                  tap(() => {
                                    stateContext.patchState({Counter: 1}); // patch state - Counter = 1
                                  })
                                );
                            }
                          } as Action)),
                          mergeMap((newState) => store.dispatch({
                            handleState(stateContext: StateContext<any>): Observable<void> {

                              // the returned state from dispatch is always new state - Counter = 1
                              expect(newState.Counter).toBe(1); // new state - Counter = 1

                              // the state of the stateContext is also new state - Counter = 1
                              expect(stateContext.getState().Counter).toBe(1); // new state - Counter = 1

                              return of(null)
                                .pipe(
                                  tap(() => {
                                    stateContext.patchState({Counter: 2}); // patch state - Counter = 2
                                  })
                                );
                            }
                          } as Action))
                        )
                        .subscribe(newState => {

                          // the returned state from dispatch is always new state - Counter = 2
                          expect(newState.Counter).toBe(2); // new state - Counter = 2

                          // the returned state from selectOnce is stale state - Counter = 1
                          store.selectOnce('Counter').subscribe((staleCounterValue) => {
                            expect(staleCounterValue).toBe(1); // stale state - Counter = 1
                            done();
                          });
                        });
                    });
                  });
                  

ObjectComparator

export type ObjectComparator = (oldObject: any, newObject: any) => boolean; - is a function. The ObjectComparator can be used by Store.select(string, ObjectComparator?): Observable<any> as second parameter. By the ObjectComparator you may implement more advanced comparison of the potential changes.
The method must return true if there are no changes you are interested in. For example the property/properties of some state object was/were not changed.

Pseudo code example:
                  this.store.select('<some state>', (oldObjectState: any, newObjectState: any) => {
                    // here you can compare if the value of some property was changed
                    if (oldObjectState === newObjectState || oldObjectState && newObjectState
                      && oldObjectState['someProperty'] === newObjectState['someProperty']) {
                      // nothing changed
                      return true;
                    }

                    // someProperty of the state object was changed
                    return false;
                  }).subscribe((state: S) => {
                    // ... do somethig if the newObjectState partly changed
                  });
                  

StateHelper

Besides the simply structured JSON objects, also the objects of the types: Date, Map and Set may be used in the state. The StateHelper can make them immutable and clone them accordingly. Also the objects with any kind of cyclic dependencies of its properties may be used in the state, for example composite or hierarchical properties or lists with the parent child relations where the leaves reference each other in different ways. The StateHelper can make them immutable and clone them respectively, and it keeps the relations.

  • deepFreeze - static deepFreeze<T>(o: T): T - freezes the object, the object is read only after the call. Also the objects/properties of the types: Sat, Map and Date are immutable. The freeze process is based on JavaScript Object.freeze().
  • cloneObject - static cloneObject<T>(o: T, cloneFunctions = true): T - creates a clone of the object including functions (default), the method is useful if the object was frozen by the deepFreeze. The method handles also objects with any kind of cyclic dependencies of its properties. Cloning the functions is in experimental stage. Usually the state objects do not have any functions, and it is not recommended they have any.

StateContext

  • getState - getState(): S - returns the whole current state.
  • patchState - patchState(val: Partial<S>) - patch the existing state with the provided value.
  • setState - setState(state: S) - reset the whole state to a new value.

Action

  • handleState - abstract handleState(stateContext: StateContext<any>): Observable<any> | void - it must be implemented by the user.
  • clone - clone<T>(o: T, cloneFunctions = true): T - clone the object including functions (default), the same as StateHelper.cloneObject(o, cloneFunctions = true). Cloning the functions is in experimental stage. Usually the state objects do not have any functions, and it is not recommended they have any.

Sources & Example

The source code can be downloaded from the GitHub https://github.com/it-and-services/state-store.

ngx-state-store : NPM module online https://www.npmjs.com/package/ngx-state-store.

state-store : The Simple example and More complex use case presented here in the documentation are running as the online App https://it-and-services.github.io/state-store/app.html.

Changelog

The history section consists of two parts. The first part is regarding the ngx-state-store changes. The second part is regarding the documentation changes.


Ngx-State-Store Changelog


                                        -----------------------------------------------------------------------------------------
                                        Version 1.1.1 - 03 Mar 2023
                                        -----------------------------------------------------------------------------------------
                                       - performance optimization
                                       - clean up deprecations

                                        -----------------------------------------------------------------------------------------
                                        Version 1.1.0 - 03 Feb 2023
                                        -----------------------------------------------------------------------------------------
                                       - clean up RxJs deprecations that will be removed in v8, the clean up was done
                                         with backward compatibility
                                       - new comfort methods presented:
                                         - selectOrDefault(...) returns default value if the state is undefined or null
                                         - selectSubProperty(...) returns state object property by path
                                         - selectSubPropertyOrDefault(...) returns state object property by path or
                                           default value if the property is undefined or null
                                         - duplicate methods of the listed above for single select - selectOnce...(...)

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.18 - 28 Oct 2021
                                        -----------------------------------------------------------------------------------------
                                        - the Angular update remark was moved out of the release into the documentation
                                          to avoid empty releases just because of the Angular update

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.17 - 21 May 2021
                                        -----------------------------------------------------------------------------------------
                                        - updated and tested up to the Angular 12

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.16 - 29 Apr 2021
                                        -----------------------------------------------------------------------------------------
                                        - Date, Map and Set objects may be used in the state
                                        - StateHelper.cloneObject() was extended to clone Date, Map and Set
                                        - StateHelper.deepFreeze() was extended to make Date, Map and Set immutable

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.15 - 14 Apr 2021
                                        -----------------------------------------------------------------------------------------
                                        - cyclic dependencies resolution by the StateHelper.cloneObject()
                                        - prevent to clone the window by the StateHelper.cloneObject()
                                        - prevent to freeze the window by the StateHelper.deepFreeze()
                                        - check return value of the Action.handleState() by rxjs isObservable()

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.14 - 28 Mar 2021
                                        -----------------------------------------------------------------------------------------
                                        - externalize the documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.13 - 20 Nov 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.12 - 20 Nov 2020
                                        -----------------------------------------------------------------------------------------
                                        - updated to Angular 11
                                        - extend performance plugin by the 'limit' parameter, default value is 1000
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.11 - 13 Jul 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation
                                        - update example application

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.10 - 26 Jun 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.9 - 28 Apr 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.8 - 25 Mar 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.7 - 10 Feb 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.6 - 30 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - hide second parameter in the StateHelper.cloneObject(any)

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.5 - 28 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation
                                        - increase test coverage

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.4 - 14 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - add clone Date into the StateHelper.cloneObject()

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.3 - 8 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - add keywords into the package.json

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.2 - 7 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation
                                        - the plugins are always enabled

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.1 - 6 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - improve documentation
                                        - improve build process
                                        - increase test coverage

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.0 - 5 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - first release
                                        - supported applications with Angular version >= 7.2.0

                                        -----------------------------------------------------------------------------------------
                                        Version 0.0.1 - 4 Jan 2020
                                        -----------------------------------------------------------------------------------------
                                        - first beta release


                            


Ngx-State-Store Documentation Changelog


                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.10 - 06 Jan 2024
                                        -----------------------------------------------------------------------------------------
                                        - Remark about Angular update/test upto the version 17

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.9 - 02 Jul 2023
                                        -----------------------------------------------------------------------------------------
                                        - Remark about Angular update/test upto the version 16

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.8 - 03 Mar 2023
                                        -----------------------------------------------------------------------------------------
                                        - New ngx-state-store 1.1.1 release

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.7 - 03 Feb 2023
                                        -----------------------------------------------------------------------------------------
                                        - New ngx-state-store 1.1.0 release

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.6 - 06 Jun 2023
                                        -----------------------------------------------------------------------------------------
                                        - Remark about Angular update upto the version 15

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.5 - 28 Aug 2022
                                        -----------------------------------------------------------------------------------------
                                        - Remark about "cloneFunctions = true"; Cloning the functions is in
                                          experimental stage. Usually the state objects do not have any functions,
                                          and it is not recommended they have any.

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.4 - 20 Jun 2022
                                        -----------------------------------------------------------------------------------------
                                        - Remark about Angular update upto the version 14

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.3 - 8 Nov 2021
                                        -----------------------------------------------------------------------------------------
                                        - Remark about Angular update upto the version 13

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.2 - 21 May 2021
                                        -----------------------------------------------------------------------------------------
                                        - StateHelper functional description
                                          see: 'API overview/StateHelper' section

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.1 - 29 Apr 2021
                                        -----------------------------------------------------------------------------------------
                                        - introduction of dispatch rules
                                          see: 'API overview/Store/dispatch rules' section

                                        -----------------------------------------------------------------------------------------
                                        Version 1.0.0 - 28 Mar 2021
                                        -----------------------------------------------------------------------------------------
                                        - initial HTML based documentation


              

TM Trademarks

JavaScript is a registered trademark of Oracle Corporation.

TypeScript is a registered trademark of Lotus Development Corporation.

IntelliJ is a registered trademarks of JetBrains s.r.o.

WebStorm is a registered trademarks of JetBrains s.r.o.

Microsoft is a registered trademark of Microsoft Corporation.

Visual Studio Code is a registered trademark of Microsoft Corporation.

All other product, company, and service names as well as company logos mentioned are the property of their respective owners and are mentioned for identification purposes only.

Copyright and license

Code released under the MIT License.

For more information about copyright and license check choosealicense.com.