Last active
July 21, 2017 09:13
-
-
Save ovangle/7191c77407e212eadc5d1d636a19c50d to your computer and use it in GitHub Desktop.
Modularised application state
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Set, Map} from 'immutable'; | |
import {Action, Reducer} from "@ngrx/store"; | |
import {Type} from "@angular/core"; | |
type TypeToken = Type<any>; | |
export interface StatefulModule<T> { | |
ngModule: Type<T>; | |
reducer<TState>(state: TState, action: Action<any>): TState; | |
/** | |
* The initial module state. | |
*/ | |
initState?: any; | |
/** | |
* By default, a module reducer is only applied to actions from it's own module, | |
* or actions without an ngModule. | |
* | |
* Actions defined in the following set will also trigger the module's reducer. | |
*/ | |
watch?: Set<TypeToken>; | |
} | |
export class AppState { | |
constructor(public statefulModules: Map<TypeToken, StatefulModule<any>>, | |
public moduleStates: Map<TypeToken,any>) { | |
} | |
} | |
export class RegisterStatefulModule<T,S> implements Action<StatefulModule<T,S>> { | |
static ngModule = AppModule; | |
constructor(public payload: StatefulModule<T,S>) {} | |
} | |
export function appStateReducer(state: AppState, action: Action<any>) { | |
let actionType = actionType(action); | |
if (type === RegisterStatefulModule) { | |
let module: RegisterStatefulModule<any,any> = action.payload; | |
return new AppState( | |
ngModules.set(module.ngModule, module), | |
moduleState.set(module.ngModule, module.initState) | |
); | |
} | |
let actionModule = actionType(action).ngModule; | |
// By default (action type has no ngModule), apply to every state. | |
let applyModules = state.statefulModules; | |
if (actionModule) { | |
// If this action has a module, only apply it to: | |
// - the module it was defined in | |
// - any additional modules that `watch` the action's module. | |
applyModules = applyModules | |
.filter(module => module.ngModule === actionType || module.watch.contains(actionType)); | |
} | |
let updatedStates = applyModules.map(module => { | |
let state = this.moduleStates.get(module.ngModule); | |
return module.reducer(state, action); | |
}); | |
return new AppState(this.statefulModules, this.moduleStates.merge(updatedStates)); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {NgModule} from '@angular/core'; | |
import {RegisterStatefulModule} from './app-state'; | |
@NgModule({/* etc. */}) | |
export class MyModule { | |
/** | |
* This may not work, I didn't actually check angular to determine whether `NgModule` were | |
* actually every constructed (or dependency injected). The issue I wrote this for was closed | |
* before I got the chance. | |
* | |
* If not, the `StatefulModule` could be added as a service in `Module.dependencies`. | |
*/ | |
constructor(store: Store<AppState>) { | |
store.dispatch(new RegisterStatefulModule({ | |
ngModule: MyModule, | |
reducer: myModuleReducer, | |
initState: new MyModuleState(), | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment