第一步:安裝相關套件
npm install @ngrx/core @ngrx/store @ngrx/effects @ngrx/router-store @ngrx/store-devtools reselect --save
第二步:建立目錄
在 src/app 底下
mkdir store
cd store
touch index.ts
mkdir actions
mkdir effects
mkdir reducers
mkdir selectors
並各自建立一個 index.ts,完成後如下
.
├── actions
│ └── index.ts
├── effects
│ └── index.ts
├── index.ts
├── reducers
│ └── index.ts
└── selectors
└── index.ts
第三步:修改 src/app/store/index.ts
如下
export * from './actions';
export * from './reducers';
export * from './selectors';
export * from './effects';
先填一些東西讓 Compiler 不要抱怨
store/actions/index.ts
export const actions = 'Actions';
store/reducers/index.ts
export const reducers = 'Reducers';
store/effects/index.ts
export const effects = 'Effects';
store/selectors/index.ts
export const selectors = 'Selectors';
第四步:修改 src/app/app.module.ts
//... 省略
// for ngrx/store
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { StoreRouterConnectingModule, RouterStateSerializer } from '@ngrx/router-store';
import { environment } from '../environments/environment';
import * as fromStore from './store';
//... 省略
@NgModule({
//...
imports: [
//...
HttpClientModule,
StoreModule.forRoot(fromStore.reducers),
EffectsModule.forRoot(fromStore.effects),
!environment.production ? StoreRouterConnectingModule : [],
!environment.production ? StoreDevtoolsModule.instrument({ maxAge: 50 }) : [],
//...
],
//...
index.ts
將所有 store 的東西包起來,在 StoreModule.forRoot()
跟 EffectsModule.forRoot()
傳進 fromStore.reducers
跟 fromStore.effects
StoreDevtoolsModule
跟 StoreRouterConnectingModule
StoreDevtoolsModule
讓我們可以使用 Chrome Extensions Redux Devtools
來看 ngrx/store 的 Action 跟 State第五步:安裝 Chrome Extensions Redux Devtools
到 Google Chrome 擴充功能,尋找 Redux
安裝後,點擊它會出現
這是一個很強大的工具,官方文件,我們在開發時,會隨時用這個工具來除錯,可以看到現在我們的 store 跟 effects 已經初始化 (init),也可以看到 ROUTER_NAVIGATION
我們習慣上先定義動作(Actions),動作在 ngrx/store 裡其實是一個很簡單的物件,它的類別 (Class)如下
第一步: 在 store/actions/ 下建立 Class 檔案,並建立 spec 檔案 (測試用)
ng generate class user.actions --spec
第二步:修改 store/actions/user.actions.ts
import { Action } from '@ngrx/store';
import { User } from '../../models';
// define action types
export const LOGIN = '[user] LOGIN';
export const LOGIN_SUCCESS = '[user] LOGIN_SUCCESS';
export const LOGIN_FAIL = '[user] LOGIN_FAIL';
export const LOGOUT = '[user] LOGOUT';
export const LOGOUT_SUCCESS = '[user] LOGOUT_SUCCESS';
export const LOGOUT_FAIL = '[user] LOGOUT_FAIL';
export const GETUSER = '[user] GETUSER';
export const GETUSER_SUCCESS = '[user] GETUSER_SUCCESS';
export const GETUSER_FAIL = '[user] GETUSER_FAIL';
// define Actions class
export class LoginAction implements Action {
readonly type = LOGIN;
constructor(public payload: User) { }
}
export class LoginSuccessAction implements Action {
readonly type = LOGIN_SUCCESS;
constructor(public payload: string) { }
}
export class LoginFailAction implements Action {
readonly type = LOGIN_FAIL;
constructor(public payload: any) { }
}
export class LogoutAction implements Action {
readonly type = LOGOUT;
}
export class LogoutSuccessAction implements Action {
readonly type = LOGOUT_SUCCESS;
}
export class LogoutFailAction implements Action {
readonly type = LOGOUT_FAIL;
}
export class GetUserAction implements Action {
readonly type = GETUSER;
}
export class GetUserSuccessAction implements Action {
readonly type = GETUSER_SUCCESS;
constructor(public payload: string) { } // username
}
export class GetUserFailAction implements Action {
readonly type = GETUSER_FAIL;
constructor(public payload: any) { }
}
export type UserActions
= LoginAction
| LoginSuccessAction
| LoginFailAction
| LogoutAction
| LogoutSuccessAction
| LogoutFailAction
| GetUserAction
| GetUserSuccessAction
| GetUserFailAction;
[user] LOGIN
payload
,有些不帶,帶 payload
的類別在這裡定義它的型態UserActions
型態 (type)new LoginAction()
的方式來產生物件第三步:修改 app/store/actions/index.ts
//export const actions = 'Actions'; 刪除
export * from './user.actions';
未來加新的動作檔案,只要在這裡做 export,這樣就定義好我們在會員模組用到的動作,基本上分為三類,Login, Logout 跟 GetUser
第一步:建立檔案 src/app/store/reducers 下
ng generate class user.reducers --spec
第二步:修改 user.reducers.ts
import { ActionReducer, Action, ActionReducerMap } from '@ngrx/store';
import * as actions from '../actions';
export interface UsersState {
isLogin: boolean;
currentUser: string;
}
const initialState: UsersState = {
isLogin: false,
currentUser: ''
}
export function reducer(state: UsersState = initialState, action: actions.UserActions): UsersState {
switch (action.type) {
case actions.LOGOUT:
return initialState;
case actions.LOGIN_SUCCESS:
case actions.GETUSER_SUCCESS:
//return Object.assign({}, state, { currentUser: action.payload, isLogin: true });
return { ...state, currentUser: action.payload, isLogin: true };
default:
return state
}
}
// for selector
export const getIsLogin = (state: UsersState) => state.isLogin;
export const getCurrentUser = (state: UsersState) => state.currentUser;
BehaviorSubject
([ngrx/store-11] Store 架構加入最簡單的 Reducer),我們需要給一個初始值Object.assign({}, state, {currentUser: action.payload, isLogin: true});
或者擴展語法 {...state, currentUser: action.payload, isLogin: true};
selector
,來讓使用者(元件或服務)訂閱最新的狀態。第三步:修改 app/store/reducers/indext.ts
import { ActionReducerMap } from '@ngrx/store';
import * as user from './user.reducers';
export interface State {
user: user.UsersState
}
export const reducers: ActionReducerMap<State> = {
user: user.reducer
}
index.ts
的方式讓將來要擴充 store 的時候比較方便,後面會陸續看到加入的 狀態跟reducer。下次我們來完成會員篇的 Effects 跟 Selector,以及更新元件