Actions/reducerutilityforNGRX.ItprovidesahandfuloffunctionstomakeNGRX/ReduxmoreAngular-tastic.
@Store(MyInitialState):Decoratorfordefaultstateofastore.@Action(...MyActionClass:Action[]):Decoratorforaactionfunction.@Effect(...MyActionClass:Action[]):Decoratorforaeffectfunction.ofAction(MyActionClass):LettableoperatorforNGRXEffectscreateReducer(MyStoreClass):Reducerbootstrapfunction@Select('my.prop'):SelectdecoratorInspiredbyredux-actandredux-actionsforRedux.
Seechangelogforlatestchanges.
NOTE:IrecommendcheckingoutmylatestlibrarycalledNGXS.
Whatsthisfor?ThisissugartohelpreduceboilerplatewhenusingReduxpatterns.Thatsaid,here'sthehighlevelofwhatitprovides:
ReducersbecomeclassessoitsmorelogicalorganizationAutomaticallycreatesnewinstancessoyoudon'thavetohandlespreadseverywhereEnablesbettertypecheckinginsideyouractionsReduceshavingtopasstypeconstantsbyusingtypecheckingItsdeadsimple(<100LOC)andyoucanpickandchoosewhereyouwanttouseit.
GettingStartedTogetstarted,letsinstallthepackagethrunpm:
npmingrx-actions--SReducersNext,createanactionjustlikeyoudowithNGRXtoday:
exportclassMyAction{readonlytype='MyAction';constructor(publicpayload:MyObj){}}thenyoucreateaclassanddecorateitwithaStoredecoratorthatcontainstheinitialstateforyourreducer.WithinthatclassyoudefinemethodsdecoratedwiththeActiondecoratorwithanargumentoftheactionclassyouwanttomatchiton.
import{Store,Action}from'ngrx-actions';@Store({collection:[],selections:[],loading:false})exportclassMyStore{@Action(Load,Refresh)load(state:MyState,action:Load){state.loading=true;}@Action(LoadSuccess)loadSuccess(state:MyState,action:LoadSuccess){state.collection=[...action.payload];}@Action(Selection)selection(state:MyState,action:Selection){state.selections=[...action.payload];}@Action(DeleteSuccess)deleteSuccess(state:MyState,action:DeleteSuccess){constidx=state.collection.findIndex(r=>r.myId===action.payload);if(idx===-1){returnstate;}constcollection=[...state.collection];collection.splice(idx,1);return{...state,collection};}}Youmaynotice,Idon'treturnthestate.Thatsbecauseifitdoesn'tseeastatereturnedfromtheactionitinspectswhetherthestatewasanobjectorarrayandautomaticallycreatesanewinstanceforyou.Ifyouaremutatingdeeplynestedproperties,youstillneedtodealwiththoseyourself.
Youcanstillreturnthestateyourselfanditwon'tmesswithit.Thisishelpfulforifthestatedidn'tchangeoryouhavesomecomplexlogicgoingon.ThiscanbeseeninthedeleteSuccessaction.
Aboveyoumaynotice,thefirstactionhasmultipleactionclasses.Thatsbecausethe@Actiondecoratorcanacceptsingleormultipleactions.
TohookituptoNGRX,allyouhavetodoiscallthecreateReducerfunctionpassingyourstore.NowpassthemyReducerjustlikeyouwouldafunctionwithaswitchstatementinside.
import{createReducer}from'ngrx-actions';exportfunctionmyReducer(state,action){returncreateReducer(MyStore)(state,action);}Intheaboveexample,IreturnafunctionthatreturnsmycreateReducer.ThisisbecauseAoTcomplainsstatingFunctionexpressionsarenotsupportedindecoratorsifwejustassignthecreateReducermethoddirectly.ThisisaknownissueandotherNGRXthingssufferfromittoo.
Next,passthattoyourNGRXmodulejustlikenormal:
@NgModule({imports:[StoreModule.forRoot({pizza:pizzaReducer})]})exportclassAppModule{}OptionallyyoucanalsoprovideyourstoredirectlytotheNgrxActionsModuleanditwillhandlecreatingthereducerforyouandalsoenablestheabilitytouseDIwithyourstores.SoratherthandescribinginforRootorforFeaturewithStoreModule,wecallthemonNgrxActionsModule.
@NgModule({imports:[NgrxActionsModule.forRoot({pizza:PizzaStore})],providers:[PizzaStore]})exportclassAppModule{}EffectsIfyouwanttouseNGRXeffects,I'vecreatedalettableoperatorthatwillallowyoutopasstheactionclassastheargumentlikethis:
import{ofAction}from'ngrx-actions';@Injectable()exportclassMyEffects{constructor(privateupdate$:Actions,privatemyService:MyService){}@Effect()Load$=this.update$.pipe(ofAction(Load),switchMap(()=>this.myService.getAll()),map(res=>newLoadSuccess(res)));}In3.x,weintroducedanewdecoratorcalled@Effectthatyoucandefineinyourstoretoperformasyncoperations.
@Store({delievered:false})exportclassPizzaStore{constructor(privatepizzaService:PizzaService){}@Action(DeliverPizza)deliverPizza(state){state.delivered=false;}@Effect(DeliverPizza)deliverPizzaToCustomer(state,{payload}:DeliverPizza){this.pizzaService.deliver(payload);}}Effectsarealwaysrunafteractions.
SelectsWedidn'tleaveoutselectors,thereisaSelectdecoratorthatacceptsa(deep)pathstring.Thislookslike:
@Component({...})exportclassMyComponent{//Functions@Select((state)=>state.color)color$:Observable<string>;//Arrayofprops@Select(['my','prop','color'])color$:Observable<strinv>;//Deeplynestedproperties@Select('my.prop.color')color$:Observable<string>;//Impliedbythenameofthemember@Select()color:Observable<string>;//Remaptheslicetoanewobject@Select(state=>state.map(f=>'blue'))color$:Observable<string>;}Thiscanhelpcleanupyourstoreselects.Tohookitup,intheAppModuleyoudo:
import{NgrxActionsModule}from'ngrx-actions';@NgModule({imports:[NgrxActionsModule]})exportclassAppModule{}Andyoucanstartusingitinanycomponent.Italsoworkswithfeaturestorestoo.Note:TheSelectdecoratorhasalimitationoflackoftypecheckingduetoTypeScript#4881.
CommonQuestionsWhataboutcomposition?Wellsinceitcreatesanormalreducerfunction,youcanstilluseallthesamecompositionfnsyoualreadyuse.WillthisworkwithnormalRedux?WhileitsdesignedforAngularandNGRXitwouldworkperfectlyfinefornormalRedux.Ifthatgetsrequested,I'llbehappytoaddbettersupporttoo.DoIhavetorewritemyentireapptousethis?No,youcanusethisincombinationwiththetranditionalswitchstatementsorwhateveryouarecurrentlydoing.DoesitsupportAoT?Yesbutseeaboveexamplefordetailsonimplementation.DoesthisworkwithNGRXDevTools?Yes,itdoes.Howdoesitworkwithtesting?Everythingshouldworkthesamewaybutdon'tforgetifyouusetheselectortooltoincludethatinyourtestrunnerthough.CommunityReducingBoilerplatewithNGRX-ACTIONSAdventuresinAngular:NGRXBoilerplateIntroducingNGRX-Actions3.0
评论