Brian F爱
从专注于角图,Web技术以yobet英雄联盟及波特兰的Node.js的Google开发人员专家中学习。
Ad ·ultimatecourses.com.
Learn Angular the right way with Ultimate Courses

更新到NGRX 4

以下是更新代码以使用NGRX 4的快速指南。

以防你错过了它,ngrx 4掉了下来on July 18. I'm going to review some of the changes that I had to implement in my codebase to update to this latest release.

TypeScript v2.4 +

First, you need to ensure that you are using version 2.4+ of TypeScript. Update your project'spackage.jsonfile to:

{“devdependencies”:{"typescript":"^2.5.2"}}

Make sure you remove your project's TypeScript installation, and then runNPM.install:

$rm-rf ./node_modules/typescript $NPM.install

RxJS v5.4+

Next, ensure that you have installed the latest version of RxJS. Update your project'spackage.jsonfile to:

{"dependencies":{"rxjs":"^5.4.3"}}

再次,请确保删除项目的RXJS安装,然后运行NPM.install:

$rm-rf ./node_modules/rxjs $NPM.install

行动s

该way you define your actions using NgRx v4 is not that much different that how you defined actions using v2/3. Let's quickly review how we define actions:

// @ngrx进口{行动}"@ngrx/store";// 楷模进口{Activity}"../../models/activity";// activitiesexportconstload_activity.="[activities] Load activity";exportconstload_activity._ERROR="[activities] Load activity error";exportconstload_activity_success.="[activities] Load activity success";/** * Load activity. * @class LoadActivityAction * @implements {Action} */exportclassLoadActivityAction实施行动{readonly类型=load_activity.;构造函数(上市payload:{id:}){}}/** * Load activity error. * @class LoadActivityErrorAction * @implements {Action} */exportclassloadActivityerroraction.实施行动{readonly类型=load_activity._ERROR;构造函数(上市payload:{错误:Error}){}}/** * Load activity success. * @class LoadActivitySuccessAction * @implements {Action} */exportclassLoadActivitySuccessAction实施行动{readonly类型=load_activity_success.;构造函数(上市payload:{activity:Activity}){}}/ ** *操作类型。* @type {行动} * /export类型行动s=LoadActivityAction|loadActivityerroraction.|LoadActivitySuccessAction;

上面是加载活动(来自REST API)的三个动作的示例。随着LoadActivityAction,我也定义了loadActivityerroraction.andLoadActivitySuccessAction发生异常或成功加载活动时的操作。

A few things to note:

  • First, we import the行动interface from @ngx/store.
  • Next, we define constant string values for each action.
  • 然后,我们定义一个实现的动作类行动interface for each action. The public类型参数是只读的,并设置为常量字符串的值。我们还定义了构造函数()每个类的函数,包含公共可用的payload属性。每个有效载荷都是一个目的with the necessary properties for the action.

这应该非常熟悉NGRX v2 / 3代码,因为没有任何改变。

导航

接下来,让我们来看看我们如何使用NGRX v4导航的更改。您将注意到的第一件事是导航功能消失。这包括:走(),back()and前锋()functions defined in the @ngrx/router-store module.

So, what do we do?

简单。我们将创建自己的行动。

首先,创造一个新的src/app/actions/router.ts应用程序中的文件:

$cdsrc/app $mkdiractions $touch路由器。ts

Here is the full contents of my路由器。ts文件:

// ngrx.进口{行动}"@ngrx/store";进口{导航Extras}"@angular/router";exportconstGO=“[路由器] go”;exportconstBACK="[Router] Back";exportconstFORWARD="[Router] Forward";exportclassGo实施行动{readonly类型=GO;构造函数(上市payload:{path:any[];查询?:object;extras?:导航Extras;}){}}exportclassBack实施行动{readonly类型=BACK;}exportclassForward实施行动{readonly类型=FORWARD;}export类型行动s=Go|Back|Forward;

这应该非常熟悉先前定义的操作。在这种情况下,我们定义了三个新动作类:Go,BackandForward

Note that theGoclass's payload has three properties defined:

  • path: specify an array of commands that match Angular'sRouter.navigate()方法。
  • 查询:指定查询参数的名称/值对的对象,类似于Angular的paramparams类型
  • extras: specify an object of导航Extras

Note that the查询andextrasproperties are optional.

Next, we need to define side effects for each of these navigation classes. To do so, create a newsrc/app/effects/router.ts文件:

$cdsrc/app $mkdireffects $touch路由器。ts

Here is the full contents of thesrc/app/effects/router.ts文件:

进口{Injectable}"@angular/core";进口{Router}"@angular/router";进口{位置}"@angular/common";进口{Effect,行动s}"@ngrx/effects";进口“RXJS / Add / Operator / Do”;进口"rxjs/add/operator/map";进口*asRouterActions"../actions/router";@Injectable()exportclassRouterEffects{@Effect({dispatch:false})navigate$=这个行动$oftype.(RouterActionsGO)map((action:RouterActionsGo)=>actionpayload)do(({path,查询:queryparams.,extras})=>这个路由器navigate(path,{queryparams.,......extras}));@Effect({dispatch:false})导航$=这个行动$oftype.(RouterActionsBACK)do(()=>这个位置back());@Effect({dispatch:false})navigateForward$=这个行动$oftype.(RouterActionsFORWARD)do(()=>这个位置forward());构造函数(private行动$:行动s,private路由器:Router,private位置:位置){}}

A few things to note:

  • 一,是RouterEffects班级装饰着@Injectable()装饰师。
  • 接下来,我们为每个导航操作定义一个属性。每家酒店都装饰了@影响()装饰师。我们设置了dispatch财产价值falsein the decorator to note that the effect will not dispatch any new actions.
  • 在每个属性中,我们在其中调用适当的方法Router或者在位置通过该实例注入构造函数()功能。
  • We use the做()RXJS方法透明地执行副作用。您可以了解更多信息做()utility method atlearnrxjs.io
  • Also, note the use of themap()运营商提取payloadobject that is provided with theGoclass's constructor function.

好的,所以现在让我们看看实现。以下是我们之前的导航实施方式:

进口{go}“@ ngrx / router-store”;上市编辑行为(activity:Activity){这个商店dispatch(go(["/activities/edit",activity_id]));}

Here is the refactored navigation using the newGoaction:

进口{Go}"../actions/router";上市编辑行为(activity:Activity){这个商店dispatch(newGo({path:["/activities/edit",activity_id]}));}

A few things to note:

  • First, we no longer import the走()function from the @ngrx/router-store module. Instead, we import theGoclass from the router module we previously defined.
  • We still use theStore.dispath()派遣动作的方法。
  • We dispatch anew的例子Goclass, specifying the payload object. In this example, I am specifying only thepath属性。Note that the value of thepath与以前一样。

Effects

该effects API for registering effects has been updated. Previously you had to invoke the跑()function for each effects class in your application. This has been deprecated in favor of two new functions:托管()andforfeature.()。该托管()function is invoked in your rootAppModule, andforfeature.()is invoked in any subsequent feature modules.

例如,在我的申请之前AppModule班级装饰者是:

进口{EffectsModule}"@ngrx/effects";@NgModule({// code omitted进口:[EffectsModule(ActivityEffects),EffectsModule(备用威胁)]})exportclassAppModule{}

Now, it's been updated to use the托管()function:

进口{EffectsModule}"@ngrx/effects";进口{RouterEffects}“./effects/router”;@NgModule({// code omitted进口:[EffectsModule托管([RouterEffects])]})exportclassAppModule{}

Note that the only effects class that I am configuring in myAppModuleis theRouterEffects。While previously I had configured all of the effects classes for my entire app. Your application may be structured different than mine, but I think this is worth noting. I am now wiring up my effects in each lazy-loaded module as appropriate, rather than in the rootAppModule

Typed Payload

该@ngrx/effects module included a helperTopayload.()与联合使用的功能map()函数返回当前操作的有效载荷。虽然这很有帮助,但问题是职能回归any

这是函数的签名:

export宣布functionTopayload.(action:行动):any;

这是它如何使用的示例:

@Effect()上市delete:可观察到的<行动>=这个actionsoftype.(DELETE_ACTIVITY)map(Topayload.)SwitchMap.(payload=>{return这个activityServicedelete(payloadactivity)map(()=>newDeleteActivitySuccessAction({activity:payloadactivity}))抓住(错误=>可观察到的of(newdeleteactivityerroraction.({错误:错误})));});

在上面的例子中,请注意使用Topayload.()功能。This will return an observable with just the payload for the current action, in this case theDELETE_ACTIVITY行动。

问题是payload传递给的对象SwitchMap.()function above is of typeany。This means we have no type safety, and we do not get any help in our editor about what properties thepayloadobject has.

以下是使用键入的有效载荷与NGRX v4的示例:

@Effect()上市delete:可观察到的<行动>=这个actionsoftype.(DELETE_ACTIVITY)map((action:deleteactivitial.)=>actionpayload)SwitchMap.(payload=>{return这个activityServicedelete(payloadactivity)map(()=>newDeleteActivitySuccessAction({activity:payloadactivity}))抓住(错误=>可观察到的of(newdeleteactivityerroraction.({错误:错误})));});

We simply use a fat arrow function to specify the type of the action, in this caseDeleteActivitySuccessAction,从操作中返回有效载荷。现在payload对此的论点SwitchMap.()方法包括类型安全性。

Reducers

A lot changed in how you configure and combine your application's reducers in NgRx v4. The good thing is that nothing will change in your pure减速器()职能。

我不确定我是否遵循最佳实践,但之前我已经成立了一个单身src/app/app.reducers.tsfile for combining all of my reducers for my application:

进口{createEleLector.}“重新选择”;//导入@ngrx.进口{actionreducer.,combineReducers}"@ngrx/store";进口{compose}"@ngrx/core/compose";进口{RouterEducer.,RouterState}“@ ngrx / router-store”;进口{storefreeze}“ngrx-store-freeze”;//进口环境进口{environment}"../environments/environment";/ ** *每个减速器模块的默认导出是减速器功能本身。在*添加,每个模块都应导出描述* reducer状态的类型或界面以及任何选择器函数。`* AS` *表示法将所有导出到一个对象中。* /进口*asactivities"./activities/activities.reducers";进口*asusers"./users/users.reducers";/ ** *我们将每个减速器视为数据库中的表格。*这意味着我们的顶级状态界面只是内部状态类型的键地图。* /exportinterfaceState{activities:activitiesState;路由器:RouterState;users:usersState;}/** * Because metareducers take a reducer function and return a new reducer, * we can use our compose helper to chain them together. Here we are * using combineReducers to make our top level reducer, and then * wrapping that in storeLogger. Remember that compose applies * the result from right to left. */const减速器s={activities:activities减速器,路由器:RouterEducer.,users:users减速器};// development reducer includes storeFreeze to prevent state from being mutatedconst开发仪器:actionreducer.<State>=compose(storefreeze,combineReducers)(减速器s);//生产减速器const生产油器:actionreducer.<State>=combineReducers(减速器s);/** * The single reducer function. * @function reducer * @param {any} state * @param {any} action */exportfunction减速器(state:any,action:any){if(environmentproduction){return生产油器(state,action);}else{return开发仪器(state,action);}}/ ********************************************************** * Activities Reducers *********************************************************//** * Returns the activities state */exportconstGetActivities.State=(state:State)=>stateactivities;/** * Returns the activities. */exportconstGetActivities.=createEleLector.(GetActivities.State,activitiesGetActivities.);/** * Returns the activity. */exportconstgetActivity=createEleLector.(GetActivities.State,activitiesgetActivity);/ ********************************************************** *用户减速器********************************************************* // ** *返回用户状态。* /exportconstGetUserstate.=(state:State)=>stateusers;/** * Returns the authenticated user */exportconstgetAuthenticatedUser=createEleLector.(GetUserstate.,usersgetAuthenticatedUser);/** * Returns the authentication error. */exportconstgetAuthenticationError=createEleLector.(GetUserstate.,usersgetAuthenticationError);/ ** *如果用户被认证* /exportconstisAuthenticated=createEleLector.(GetUserstate.,usersisAuthenticated);

Wow, that's long! And the worst part is, that is just a small part of my application's reducer functions. I would guess there are over 100 functions that I had previously defined in this reducers file.

该first thing I did was to delete this file.

该n, following theexample applicationarchitecture I started by creating a减速器sdirectory in each module.

让我们专注于我的SRC / APP /活动/减速机directory. In here, I have three files:

  1. SRC / APP /活动/减速机/activities.ts
  2. SRC / APP /活动/减速商/ INDEX.TS
  3. SRC / APP /活动/减速商/模块.TS

In this modules I have multiple state objects: activities and modules. This takes advantage of NgRx's分形国家管理:

商店使用分形状态管理,通过功能模块,急切地或懒惰地提供状态组成。

让我们来看看活动性and reducer inSRC / APP /活动/减速机/activities.ts:

// import actions进口{load_activity.,load_activity._ERROR,load_activity_success.,行动s}“../actions/activity”;// import models进口{Activity}"../../models/activity";/ ** *国家。* /exportinterfaceState{activity?:Activity;错误?:Error;loading:布尔基;}/ ** *初始状态。* /constinitialState:State={loading:false,};/ ** *减速器功能。* /exportfunction减速器(state:State=initialState,action:行动s):State{switch(action类型){案件load_activity.:return{......state,......{activity:未定义,错误:未定义,loading:true}};案件load_activity._ERROR:return{......state,......{错误:actionpayload错误,loading:false}};案件load_activity_success.:return{......state,......{activity:actionpayloadactivity,loading:false}};default:returnstate;}}/** * Return true if the activity is loading */exportconstisLoading=(state:State)=>stateloading;/ ** *退回活动* /exportconstgetActivity=(state:State)=>stateactivity;

A few things to note here:

  • First, we import the action constant strings along with the行动s类型for this feature.
  • 接下来,我们定义了State接口。
  • 然后,我们定义我们的initialStateobject that implements theState接口。
  • 该n, we define the pure减速器()function that modifies our state based on the dispatched action to the store.
  • 最后,我们定义了返回返回值的状态选择器函数loading布尔基value, and theactivitythat has been loaded.

这应该非常熟悉,因为这正是我们使用NGRX v2 / 3所做的。

现在,为了清楚起见,让我们来看看SRC / APP /活动/减速商/模块.TS文件:

// import actions进口{LOAD_ACTIVITIES_FOR_MODULE,LOAD_ACTIVITIES_FOR_MODULE_ERROR,LOAD_ACTIVITIES_FOR_MODULE_SUCCESS,行动s}"../actions/module";// import models进口{Activity}"../../models/activity";进口{模块}“../../models/module”;/ ** *国家。* /exportinterfaceState{activities?:Activity[];错误?:Error;loading:布尔基;module?:模块;}/ ** *初始状态。* /constinitialState:State={activities:[],loading:false};/ ** *减速器功能。* /exportfunction减速器(state:State=initialState,action:行动s):State{switch(action类型){案件LOAD_ACTIVITIES_FOR_MODULE:return{......state,......{activities:[],错误:未定义,module:actionpayloadmodule}};案件LOAD_ACTIVITIES_FOR_MODULE_ERROR:return{......state,......{错误:actionpayload错误,loading:false}};案件LOAD_ACTIVITIES_FOR_MODULE_SUCCESS:return{......state,......{activities:actionpayloadactivities,loading:false}};default:returnstate;}}/ ** *如果活动正在加载* /exportconstareActivitiesLoading=(state:State)=>stateloading;/** * Return activities. */exportconstGetActivities.=(state:State)=>stateactivities;

这与我们活动的国家管理层不是很大。在这种情况下,我正在管理特定模块的活动状态。虽然,这是针对我的应用程序的特定,但我认为这将有助于展示我们如何撰写多个国家管理

现在,让我们看看我们的国家组成SRC / APP /活动/减速商/ INDEX.TS文件:

// ngrx.进口{createEleLector.,createFeatureSelector}"@ngrx/store";// reducers进口{Stateasrootstate.}"../../reducers";进口*as从Activities"./activities";进口*as从模块s“./modules”;exportinterface活动性{activities:从ActivitiesState;模块:从模块sState;}exportinterfaceState延伸rootstate.{“活动”:活动性;}exportconst减速器s={activities:从Activities减速器,模块:从模块s减速器};/** * The createFeatureSelector function selects a piece of state from the root of the state object. * This is used for selecting feature states that are loaded eagerly or lazily. */exportconstGetActivities.State=createFeatureSelector<活动性>(“活动”);/ ** *每个减速器模块导出选择器函数,但是儿童减速器*没有了解整体州树。为了使它们可用,我们需要制作包装它们的新选择。* * createElector函数创建非常有效的选择器,这些选项备忘录,*仅在参数更改时重新编译。创建的选择器也可以组成*一起选择不同的状态。* /exportconstgetActivityEntityState=createEleLector.(GetActivities.State,(state:活动性)=>stateactivities);exportconstgetModulesentityState.=createEleLector.(GetActivities.State,(state:活动性)=>state模块);/** * Returns the activity. */exportconstgetActivity=createEleLector.(getActivityEntityState,从ActivitiesgetActivity);/** * Returns the module activities. */exportconstgetModuleActivities=createEleLector.(getModulesentityState.,从模块sGetActivities.);

让我们回顾我们的国家成分:

  • First, we import thecreateEleLector.()andcreateFeatureSelector()functions from the @ngrx/store module.
  • 接下来,我们进口我们的根Stateinterface that is defined inSRC / APP / REDUCERS / INDEX.TS。接下来我们会看看这一点。
  • 然后,我们导入每个分形状态管理减速机。在这种情况下,我正在从每个模块导入所有导出的成员:SRC / APP /活动/减速机/activities.tsandSRC / APP /活动/减速商/模块.TS
  • 现在我们可以定义我们的组成ActivititeState接口。我叫这个,因为这是为了活动制度module in my application, which is lazy-loaded.
  • 接下来,我们定义一个Stateinterface that extends therootstate., specifying the feature state.
  • 该n, we define a减速器s包含此模块的减速器函数的对象。我们之前定义了这些。
  • 然后,使用createFeatureSelector()convenience method we create the feature state.
  • 最后,我们使用的选择器使用createEleLector.()convenience method. Note that we first specify the appropriate feature selector function, and then we specify the selector functions that we defined in each module.

I should also make sure to mention that we are no longer dependent on thecreateEleLector.()方法中的方法重新选择模块。您可以安全地删除您的依赖package.json升级到ngrx v4后的文件。

Now, let's finish upgrading our reducers by defining the rootState接口。We'll be using the @ngrx/router-store to bind Angular's router to our store.

First, I created a newSRC / APP / REDUCERS / INDEX.TS文件:

$cdsrc/app $mkdir减速剂$touch索引

Here is the full contents of theSRC / APP / REDUCERS / INDEX.TS文件:

//导入@ngrx.进口{actionreducer.,actionreducer.Map,createEleLector.,createFeatureSelector,MetaReducer}"@ngrx/store";进口{Routereducerstate.,RouterEducer.}“@ ngrx / router-store”;进口{RouterStateUrl}“../shared/utils”;//进口环境进口{environment}"../../environments/environment";/ ** *如上所述,我们将每个减速器视为数据库中的表格。这意味着*我们的顶级状态界面只是内部状态类型的键地图。* /exportinterfaceState{RouterEducer.:Routereducerstate.<RouterStateUrl>;}/** * Our state is composed of a map of action reducer functions. * These reducer functions are called with each dispatched action * and the current or initial state and return a new immutable state. */exportconst减速器s:actionreducer.Map<State>={RouterEducer.:RouterEducer.};// log all actionsexportfunction记录器(减速器:actionreducer.<State>):actionreducer.<State>{returnfunction(state:State,action:any):State{console日志("state",state);console日志("action",action);return减速器(state,action);};}/** * By default, @ngrx/store uses combineReducers with the reducer map to compose * the root meta-reducer. To add more meta-reducers, provide an array of meta-reducers * that will be composed to form the root meta-reducer. */exportconstmetaReducers:MetaReducer<State>[]=!environmentproduction?[记录器]:[];

I can't take any credit for this, as I took this straight from theexample application provided with NgRx v4

现在,让我们回到我们的AppModule看看我们的必要变化。之前,我们将路由器和存储有关如下:

进口{RouterStoreModule.}“@ ngrx / router-store”;进口{StoreModule}"@ngrx/store";进口{减速器}"./app.reducers";@NgModule({// code omitted进口:[RouterStoreModule.连接器(),StoreModule提供(减速器,{路由器:窗口位置pathname+窗口位置search}),]})exportclassAppModule{}

After our upgrade to NgRx v4 ourAppModule应该看起来像:

进口{StorerouterConnectingModule.}“@ ngrx / router-store”;进口{StoreModule}"@ngrx/store";进口{metaReducers,减速器s}"./reducers";@NgModule({// code omitted进口:[StorerouterConnectingModule.,StoreModule托管(减速器s,{metaReducers}),]})exportclassAppModule{}

A few things to note:

  • 首先,我们导入新的StorerouterConnectingModule.来自@ ngrx / router-store的模块。该RouterStoreModule.module has been deprecated in NgRx v4.
  • Second, we no longer import the single减速器功能。如果您从我的v2 / 3代码中记得(以及从V2 / 3示例应用程序),我们使用了撰写()function when in development, or thecombineeducers()在生产中的功能创建单个减速器功能。这被传递到了StoreModule.provideStore方法,也已在V4中弃用。
  • Next, we import themetaReducersand减速器s从our newSRC / APP / REDUCERS / INDEX.TS模块。
  • 该n, we specify theStorerouterConnectingModule.module in the进口array. Note that we just import the module. We do not need to invoke the连接器()像我们以前一样的方法;同样,这是在V4中弃用的。
  • Finally, we invoke the托管()method on theStoreModuleclass, specifying our减速器s

Note that we use the托管()method in this instance because this is our rootAppModule。Let's quickly review how we use theforfeature.()方法在一个功能模块。

Here is a snippet from the活动制度insrc / app /活动/行动ivities.module.ts:

进口{EffectsModule}"@ngrx/effects";进口{StoreModule}"@ngrx/store";进口{ActivityEffects}"./effects";进口{减速器s}"./reducers";@NgModule({// code omitted进口:[EffectsModuleforfeature.([ActivityEffects]),StoreModuleforfeature.(“活动”,减速器s)]})exportclass活动制度{}

A few things to note:

  • First, we import theEffectsModuleandStoreModule从@ ngrx /效果和@ ngrx /商店。
  • 该n, we import the活动效应class. These are the side effects that we defined for this feature module.
  • 该n, we import the减速器sobject that contains each state's reducer function.
  • 然后,我们调动EffectsModule.forFeature()方法,为此特征提供效果数组。
  • Finally, we invoke theStoremodule.forfeature()method, specifying the name of the feature state, and then the reducers for the state.

Conclusion

In conclusion, I highly recommend you check out some of the resources provided by the NgRx team:

Brian F爱

嗨,我是布莱恩。我对类型名称,Angular和node.js感兴趣我嫁给了我最好的朋友邦妮,我住在波特兰和我滑雪(很多)。