- Notifications
You must be signed in to change notification settings - Fork0
A simple Flux Action Creator and Flux Reducers library for @ngrx/store. Its goal is to provide simple yet type-safe experience with Flux actions. Created actions are FSA-compliant
License
tblaisot/ngrx-store-fsa-helpers
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A simple Flux Action Creator and Flux Reducers library for@ngrx/store. Its goal is to provide simpleyet type-safe experience with Flux actions.Created actions are FSA-compliant:
interfaceFSAAction<P>{type:string;payload?:P;error?:boolean;meta?:Object;}
It allows you to define reducers by chaining a series of handlers for different actiontypes and optionally providing an initial value.
This library is heavily inpired (with minor adaptations to make it @ngrx/store compatible) by two awesome librairies:
- typescript-fsa by Daniel Lytkin
- typescript-fsa-reducers by David Philipson
- Installation
- Usage
- API
- Actions
- Starting a reducer chain
- Reducer chain methods
.case(actionCreator, handler(state, payload) => newState).caseWithAction(actionCreator, handler(state, action) => newState).cases(actionCreators, handler(state, payload) => newState).casesWithAction(actionCreators, handler(state, action) => newState).withHandling(updateBuilder(builder) => builder).default(handler(state, action) => newState).build()
npm install --save ngrx-store-fsa-helpersoryarn add ngrx-store-fsa-helpersimport{actionCreatorFactory}from'ngrx-store-fsa-helpers';constactionCreator=actionCreatorFactory();// Specify payload shape as generic type argument.constsomethingHappened=actionCreator<{foo:string}>('SOMETHING_HAPPENED');// Get action creator type.console.log(somethingHappened.type);// SOMETHING_HAPPENED// Create action.constaction=somethingHappened({foo:'bar'});console.log(action);// {type: 'SOMETHING_HAPPENED', payload: {foo: 'bar'}}
Async Action Creators are objects with propertiesstarted,done andfailed whose values are action creators.
started actions will have the following shape
interfaceFSAAction<P>{type:string;payload?:P;error?:boolean;meta?:Object|null;}
done actions will have the following shape
interfaceSuccessFSAAction<P,S>{type:string;payload:{params?:P;result?:S;};error?:boolean;meta?:Object;}
failed actions will have the following shape
interfaceFailureFSAAction<P,E>{type:string;payload:{params?:P;error?:E;};error:true;meta?:Object;}
import{actionCreatorFactory}from'ngrx-store-fsa-helpers';constactionCreator=actionCreatorFactory();// specify parameters and result shapes as generic type argumentsconstdoSomething=actionCreator.async<{foo:string},// parameter type{bar:number},// success type for field "result" in payload{code:number}// error type for field "error" in payload>('DO_SOMETHING');console.log(doSomething.started({foo:'lol'}));// {type: 'DO_SOMETHING_STARTED', payload: {foo: 'lol'}}console.log(doSomething.done({params:{foo:'lol'},result:{bar:42},});// {type: 'DO_SOMETHING_DONE', payload: {// params: {foo: 'lol'},// result: {bar: 42},// }}console.log(doSomething.failed({params:{foo:'lol'},error:{code:42},});// {type: 'DO_SOMETHING_FAILED', payload: {// params: {foo: 'lol'},// error: {code: 42},// }, error: true}
You can specify a prefix that will be prepended to all action types. This isuseful to namespace library actions as well as for large projects where it'sconvenient to keep actions near the component that dispatches them.
// MyComponent.actions.tsimport{actionCreatorFactory}from'ngrx-store-fsa-helpers';constactionCreator=actionCreatorFactory('MyComponent');constsomethingHappened=actionCreator<{foo:string}>('SOMETHING_HAPPENED');constaction=somethingHappened({foo:'bar'});console.log(action);// {type: 'MyComponent/SOMETHING_HAPPENED', payload: {foo: 'bar'}}
Suppose we have the setup:
import{actionCreatorFactory}from'ngrx-store-fsa-helpers';constactionCreator=actionCreatorFactory();interfaceState{name:string;balance:number;isFrozen:boolean;}constINITIAL_STATE:State={name:"Untitled",balance:0,isFrozen:false,};constsetName=actionCreator<string>("SET_NAME");functionsetNameHandler(state:State,name:string):State{return{ ...state, name};}constaddBalance=actionCreator<number>("ADD_BALANCE");functionaddBalanceHandler(state:State,addedBalance:number):State{return{ ...state,balance:state.balance+addedBalance};}constsetIsFrozen=actionCreator<boolean>("SET_IS_FROZEN");functionsetIsFrozenHandler(state:State,isFrozen:boolean):State{return{ ...state, isFrozen};}
import{Action}from'@ngrx/store';import{isType}from'ngrx-store-fsa-helpers';functionreducer(state=INITIAL_STATE,action:Action):State{if(isType(action,setName)){returnsetNameHandler(state,action.payload);}elseif(isType(action,addBalance)){returnaddBalanceHandler(state,action.payload);}elseif(isType(action,setIsFrozen)){returnsetIsFrozenHandler(state,action.payload);}else{returnstate;}}
import{reducerWithInitialState}from'ngrx-store-fsa-helpers';constreducer=reducerWithInitialState(INITIAL_STATE).case(setName,setNameHandler).case(addBalance,addBalanceHandler).case(setIsFrozen,setIsFrozenHandler);
Everything is typesafe. If the types of the action payload and handler don't line up, thenTypeScript will complain.
The reducer builders are immutable. Each call to.case() returns a new reducer rather thanmodifying the callee.
Creates Action Creator factory with optional prefix for action types.
prefix?: string: Prefix to be prepended to action types.defaultIsError?: Predicate: Function that detects whether action is errorgiven the payload. Default ispayload => payload instanceof Error.
Returnstrue if action has the same type as action creator. DefinesType Guardthat lets TypeScript knowpayload type inside blocks whereisType returnedtrue:
constsomethingHappened=actionCreator<{foo:string}>('SOMETHING_HAPPENED');if(isType(action,somethingHappened)){// action.payload has type {foo: string};}
Starts a reducer builder-chain which uses the provided initial state if passedundefined as its state. For example usage, see theUsage sectionabove.
Starts a reducer builder-chain without special logic for an initial state.undefined will be treated like any other value for the state.
Redux seems to really want you to provide an initial state for your reducers.ItscreateStore API encourages it andcombineReducers function enforces it.For the Redux author's reasoning behind this, seethisthread. For this reason,reducerWithInitialState will likely be the more common choice, but the optionto not provide an initial state is there in case you have some means ofcomposing reducers for which initial state is unnecessary.
Note that since the type of the state cannot be inferred from the initial state,it must be provided as a type parameter:
constreducer=reducerWithoutInitialState<State>().case(setName,setNameHandler).case(addBalance,addBalanceHandler).case(setIsFrozen,setIsFrozenHandler);
Starts a builder-chain which produces a "reducer" whose return type is asupertype of the input state. This is most useful for handling a state which maybe in one of several "modes", each of which responds differently to actions andcan transition to the other modes. Many applications will not have a use forthis.
Note that the function produced is technically not a reducer because the initialand updated states are different types.
Example usage:
typeState=StoppedState|RunningState;interfaceStoppedState{ type:"STOPPED";}interfaceStartedState{ type:"STARTED"; count:number;}constINITIAL_STATE:State={type:"STOPPED"};conststartWithCount=actionCreator<number>("START_WITH_COUNT");constaddToCount=actionCreator<number>("ADD_TO_COUNT");conststop=actionCreator<void>("STOP");functionstartWithCountHandler(state:StoppedState,count:number):State{return{type:"STARTED", count};}functionaddToCountHandler(state:StartedState,count:number):State{return{ ...state,count:state.count+count};}functionstopHandler(state:StartedState):State{return{type:"STOPPED"};}conststoppedReducer=upcastingReducer<StoppedState,State>().case(startWithCount,startWithCountHandler);conststartedReducer=upcastingReducer<StartedState,State>().case(addToCount,addToCountHandler).case(stop,stopHandler);functionreducer(state=INITIAL_STATE,action:Redux.Action):State{if(state.type==="STOPPED"){returnstoppedReducer(state,action);}elseif(state.type==="STARTED"){returnstartedReducer(state,action);}else{thrownewError("Unknown state");}}
Mutates the reducer such that it applieshandler when passed actions matchingthe type ofactionCreator. For examples, seeUsage.
Like.case(), except thathandler receives the entire action as its secondargument rather than just the payload. This is useful if you want to read otherproperties of the action, such asmeta orerror, or if you want to pass theentire action unmodified to some other function. For an example, seeUsage.
Like.case(), except that multiple action creators may be provided and thesame handler is applied to all of them. That is,
reducerWithInitialState(initialState).cases([setName,addBalance,setIsFrozen],handler,);
is equivalent to
reducerWithInitialState(initialState).case(setName,handler).case(addBalance,handler).case(setIsFrozen,handler);
Note that the payload passed to the handler may be of the type of any of thelisted action types' payloads. In TypeScript terms, this means it has typeP1 | P2 | ..., whereP1, P2, ... are the payload types of the listed actioncreators.
The payload type is inferred automatically for up to four action types. Afterthat, it must be supplied as a type annotation, for example:
reducerWithInitialState(initialState).cases<{documentId:number}>([selectDocument,editDocument,deleteDocument,sendDocument,archiveDocument,],handler);
Like.cases(), except that the handler receives the entire action as itssecond argument rather than just the payload.
Convenience method which applies the provided function to the current builderand returns the result. Useful if you have a sequence of builder updates (callsto.case(), etc.) which you want to reuse across several reducers.
Produces a reducer which applieshandler when no previously added.case(),.caseWithAction(), etc. matched. The handler is similar to the one in.caseWithAction(). Note that.default() ends the chain and internally doesthe same as.build(), because it is not intended that the chain bemutated after calling.default().
This is useful if you have a "delegate" reducer that should be called on anyaction after handling a few specific actions in the parent.
constNESTED_STATE={someProp:"hello",};constnestedReducer=reducerWithInitialState(NESTED_STATE).case(...);constINITIAL_STATE={someOtherProp:"world"nested:NESTED_STATE};constreducer=reducerWithInitialState(INITIAL_STATE).case(...).default((state,action)=>({ ...state,nested:nestedReducer(state.nested,action),}));
Returns a plain reducer function whose behavior matches the current state of thereducer chain. Further updates to the chain (through calls to.case()) willhave no effect on this function.
There are two reasons you may want to do this:
You want to ensure that the reducer is not modified further
Calling
.build()is an example of defensive coding. It prevents someonefrom causing confusing behavior by importing your reducer in an unrelatedfile and adding cases to it.You want your package to export a reducer, but not have its types dependon
typescript-fsa-reducersIf the code that defines a reducer and the code that uses it reside inseparate NPM packages, you may run into type errors since the exportedreducer has type
ReducerBuilder, which the consuming package does notrecognize unless it also depends ontypescript-fsa-reducers. This isavoided by returning a plain function instead.
Example usage:
constreducer=reducerWithInitialState(INITIAL_STATE).case(setName,setNameHandler).case(addBalance,addBalanceHandler).case(setIsFrozen,setIsFrozenHandler).build();
About
A simple Flux Action Creator and Flux Reducers library for @ngrx/store. Its goal is to provide simple yet type-safe experience with Flux actions. Created actions are FSA-compliant
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.