import { helpers } from 'ngx-tethys/util' ; import { BehaviorSubject , from , Observable , Observer , Subscription } from 'rxjs' ; import { distinctUntilChanged , map , shareReplay } from 'rxjs/operators' ; import { Directive , isDevMode , OnDestroy } from '@angular/core' ; import { Action } from './action' ; import { ActionState } from './action-state' ; import { getSingletonRootStore , RootStore } from './root-store' ; import { META_KEY , StoreMetaInfo } from './types' ; interface Action { type : string ; payload ? : any ; } @ Directive ( ) export class Store < T = unknown > implements Observer < T > , OnDestroy { initialStateCache : any ; public state$ : BehaviorSubject < T > ; public reduxToolEnabled = isDevMode ( ) ; private _defaultStoreInstanceId : string ; constructor ( initialState ) { this . _defaultStoreInstanceId = this . _getClassName ( ) ; this . state$ = new BehaviorSubject < T > ( initialState ) ; this . initialStateCache = { ... initialState } ; if ( this . reduxToolEnabled ) { const rootStore = getSingletonRootStore ( ) ; ActionState . changeAction ( `Add-${ this . _defaultStoreInstanceId }` ) ; rootStore . registerStore ( this ) ; } } get snapshot ( ) { return this . state$ . getValue ( ) ; } public dispatch ( type , payload ? ) { ActionState . changeAction ( `${ this . _defaultStoreInstanceId }-${ type }` ) ; const result = this . _dispatch ( { type : type , payload : payload } ) ; result . subscribe ( ) ; return result ; } private _dispatch ( action ) { const meta = this [ META_KEY ] as StoreMetaInfo ; if ( ! meta ) { throw new Error ( `${ META_KEY } is not found, current store has not action` ) ; } const actionMeta = meta . actions [ action . type ] ; if ( ! actionMeta ) { throw new Error ( `${ action . type } is not found` ) ; } let result = actionMeta . originalFn . call ( this , this . snapshot , action . payload ) ; if ( result instanceof Promise ) { result = from ( result ) ; } if ( result instanceof Observable ) { result = result . pipe ( map ( r => r ) ) ; } else { result = new Observable ( ( observer ) => { observer . next ( { } ) ; } ) ; } return result . pipe ( shareReplay ( ) ) ; } select < TResult > ( selector ) ; select ( selector ) { return this . state$ . pipe ( map ( selector ) , distinctUntilChanged ( ) ) ; } next ( state ) { this . state$ . next ( state ) ; } error ( error ) { this . state$ . error ( error ) ; } complete ( ) { this . state$ . complete ( ) ; } subscribe ( next ? , error ? , complete ? ) { return this . state$ . subscribe ( next , error , complete ) ; } example param fn setState ( fn ) { if ( helpers . isFunction ( fn ) ) { this . next ( { ... this . snapshot , ... ( fn as any ) ( this . snapshot ) } ) ; } else { this . next ( { ... this . snapshot , ... ( fn as T ) } ) ; } } getState ( ) { return this . snapshot ; } @ Action ( ) clearState ( ) { this . setState ( this . initialStateCache ) ; } ngOnDestroy ( ) { if ( this . reduxToolEnabled ) { const rootStore = getSingletonRootStore ( ) ; rootStore . unregisterStore ( this ) ; } } /**
     * You can override this method if you want to give your container instance a custom id.
     * The returned id must be unique in the application.
     */ getStoreInstanceId ( ) { return this . _defaultStoreInstanceId ; } private _getClassName ( ) { const name = this . constructor . name || /function (.+)\(/ . exec ( this . constructor + '' ) [ 1 ] ; if ( this . reduxToolEnabled ) { const rootStore = getSingletonRootStore ( ) ; if ( ! rootStore . existStoreInstanceId ( name ) ) { return name ; } let j = 0 ; for ( let i = 1 ; i < 20 ; i ++ ) { if ( ! rootStore . existStoreInstanceId ( `${ name }-${ i }` ) ) { j = i ; break ; } } return `${ name }-${ j }` ; } return name ; } }