projects/ng-simple-state/src/lib/rxjs/ng-simple-state-base-store.ts
import { Inject, Injectable, Injector, OnDestroy, Directive } from '@angular/core';
import { BehaviorSubject, Observable, asyncScheduler } from 'rxjs';
import { map, distinctUntilChanged, observeOn } from 'rxjs/operators';
import { NgSimpleStateBaseCommonStore } from '../ng-simple-state-common';
@Injectable()
@Directive()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class NgSimpleStateBaseRxjsStore<S extends object | Array<any>> extends NgSimpleStateBaseCommonStore<S> implements OnDestroy {
protected stackPoint: number = 3;
private readonly state$: BehaviorSubject<S>;
/**
* Return the observable of the state
* @returns Observable of the state
*/
public get state(): Observable<S> {
return this.state$.asObservable();
}
constructor(@Inject(Injector) injector: Injector) {
super(injector)
this.state$ = new BehaviorSubject<S>(Object.assign(this.isArray ? [] : {}, this.firstState));
}
/**
* When you override this method, you have to call the `super.ngOnDestroy()` method in your `ngOnDestroy()` method.
*/
override ngOnDestroy(): void {
super.ngOnDestroy();
this.state$.complete();
}
/**
* Select a store state
* @param selectFn State selector (if not provided return full state)
* @param comparator A function used to compare the previous and current state for equality. Defaults to a `===` check.
* @returns Observable of the selected state
*/
selectState<K>(selectFn?: (state: Readonly<S>) => K, comparator?: (previous: K, current: K) => boolean): Observable<K> {
if (!selectFn) {
selectFn = (tmpState: Readonly<S>) => Object.assign(this.isArray ? [] : {}, tmpState) as K;
}
if (!comparator && this.comparator) {
comparator = this.comparator;
}
return this.state$.pipe(
map(state => (selectFn as (state: Readonly<S>) => K)(state as Readonly<S>)),
distinctUntilChanged(comparator),
observeOn(asyncScheduler)
);
}
/**
* Return the current store state (snapshot)
* @returns The current state
*/
getCurrentState(): Readonly<S> {
return this.deepFreeze(this.state$.getValue());
}
/**
* Set a new state
* @param selectFn State reducer
* @param actionName The action label into Redux DevTools (default is parent function name)
* @returns True if the state is changed
*/
setState(stateFn: (currentState: Readonly<S>) => Partial<S>, actionName?: string): boolean {
const currState = this.getCurrentState();
const newState = stateFn(currState);
if (currState === newState) {
return false;
}
let state: S;
if (this.isArray) {
state = Object.assign([] as S, newState);
} else {
state = Object.assign({}, currState, newState);
}
this.state$.next(state);
this.devToolSend(state, actionName);
this.statePersist(state);
return true;
}
}
@Injectable()
@Directive()
/**
* @deprecated use NgSimpleStateBaseRxjsStore
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class NgSimpleStateBaseStore<S extends object | Array<any>> extends NgSimpleStateBaseRxjsStore<S> {
constructor(@Inject(Injector) injector: Injector) {
super(injector);
if (this.devMode) {
console.warn('NgSimpleStateBaseStore is deprecated. Please use NgSimpleStateBaseRxjsStore')
}
}
}