import { Observable } from 'rxjs' ; import { distinctUntilChanged } from 'rxjs/operators' ; import { Inject , NgZone , Type } from '@angular/core' ; import { ReduxActionDispatcher , ReduxReducerDecorator , ReduxStateDecorator } from '@harmowatch/redux-decorators' ; import { ReduxStateDefinition } from '../interfaces/redux-state-definition.interface' ; import { ReduxActionWithPayload } from '../interfaces/redux-action-with-payload.interface' ; import { ReduxStateDefinitionToken } from '../tokens/redux-state-definition.token' ; import { ReduxSelector } from '../redux-selector' ; import { ReduxRegistry } from './redux-registry' ; export abstract class ReduxStateProvider < S = { } > { public static instancesByName : { [ stateName ] : ReduxStateProvider ; } = { } ; public readonly name : string ; public readonly stateDef : ReduxStateDefinition ; protected selectorCache : { [ selector ] : Observable < { } > ; } = { } ; protected reducerMethodsByType : { [ actionType ] : Function [ ] ; } ; constructor ( @ Inject ( ReduxStateDefinitionToken ) stateDefs = [ ] , private zone ) { const { name = null } = ReduxStateDecorator . get ( this . constructor ) || { } ; if ( ! name ) { throw new Error ( 'Unable to resolve state name! Make sure you\'ve decorated the provider by "@ReduxState"!' ) ; } this . name = ReduxStateDecorator . get ( this . constructor ) . name ; this . stateDef = stateDefs . find ( def => ReduxStateDecorator . get ( def . provider ) . name === name ) ; if ( ! this . stateDef ) { throw new Error ( 'Unable to resolve state definition! Make sure you\'ve registered the provider to ReduxModule!' ) ; } this . reducerMethodsByType = ( this . stateDef . reducers || [ ] ) . map ( clazz => this . getReducerMethods ( new clazz ( ) ) ) . reduce ( ( all , curr ) => [ ] . concat ( curr , all ) , [ ] ) . reduce ( ( methodsByType , reducer ) => { const type = ReduxActionDispatcher . getType ( reducer . type ) ; return { ... methodsByType , [ type ] : [ reducer . method ] . concat ( methodsByType [ type ] || [ ] ) } ; } , { } ) ; ReduxStateProvider . instancesByName [ this . name ] = this ; } getInitialState ( ) { throw new Error ( 'Method "getInitialState" not implemented.' ) ; } select < T > ( selector = '' ) { const stateType = this . constructor as Type < ReduxStateProvider < S > > ; selector = ReduxSelector . normalize ( selector , stateType ) ; if ( ! this . selectorCache [ selector ] ) { this . selectorCache [ selector ] = new ReduxSelector < T > ( this . zone , selector , stateType ) . pipe ( distinctUntilChanged ( ) ) ; } return this . selectorCache [ selector ] as ReduxSelector < T > ; } getState ( ) { return ReduxRegistry . getStore ( ) . then ( store => { return ReduxSelector . getValueByState < S > ( store . getState ( ) , '/' + this . name ) ; } ) ; } reduce < P > ( state , action ) { const reducerMethods = this . reducerMethodsByType [ action . type ] || [ ] ; return reducerMethods . reduce ( ( stateToReduce , method ) => method ( stateToReduce , action ) , state ) ; } private getReducerMethods ( reducerClassInstance ) { return Object . values ( Object . getPrototypeOf ( reducerClassInstance ) ) . map ( method => { return { method : ( method as Function ) . bind ( reducerClassInstance ) , type : ReduxReducerDecorator . get ( method ) , } ; } ) . filter ( reducer => reducer && reducer . type ) . reduce ( ( all , curr ) => all . concat ( [ ] . concat ( curr . type ) . map ( type => ( { ... curr , type } ) ) ) , [ ] ) ; } }