开发流程:
开始 -> 编写数据模型 -> 编写action -> 编写redurces并配置到相应module -> 编写services -> 编写effects并配置到相应module -> 创建组件 -> 组件绑定数据模型 -> 渲染
第一步:编写数据模型(app/models/user.ts)
export class User {
id: number;
name: string;
}
第二步:编写action(app/actions/num.ts)
import {Action} from '@ngrx/store';
import {User} from '../models/user';
export enum ListActionType {
Load = 'LOAD',
LoadSuccess = 'LOAD_SUCCESS',
RemoveUser = 'REMOVE_USER',
RemoveUserSuccess = 'REMOVE_USER_SUCCESS',
RemoveUserError = 'REMOVE_USER_ERROR'
}
export class Load implements Action {
readonly type = ListActionType.Load;
}
export class LoadSuccess implements Action {
readonly type = ListActionType.LoadSuccess;
constructor(public payload: User[]) {
}
}
export class RemoveUser implements Action {
readonly type = ListActionType.RemoveUser;
constructor(public payload: number) {
}
}
export class RemoveUserSuccess implements Action {
readonly type = ListActionType.RemoveUserSuccess;
}
export class RemoveUserError implements Action {
readonly type = ListActionType.RemoveUserError;
}
第三步:编写redurcers(app/redurces/list.ts)
import {Action} from '@ngrx/store';
import {User} from '../models/user';
import {ListActionType, LoadSuccess, RemoveUser} from '../actions/list';
export interface State {
loading: boolean;
loaded: boolean;
list: User[];
}
const initialState: State = {
loading: false,
loaded: false,
list: []
};
export const list = (state = initialState, action: Action) => {
switch (action.type) {
case ListActionType.Load:
console.log('load....');
return {
...state,
loading: true,
};
case ListActionType.LoadSuccess:
console.log('load success');
const myA = (<LoadSuccess>action).payload;
console.log(myA);
return {
...state,
loaded: true,
loading: false,
list: myA
};
case ListActionType.RemoveUser:
console.log('remove user');
const userId = (<RemoveUser>action).payload;
state.list = state.list.filter(function (item) {
return item.id !== userId;
});
return {...state};
default:
return state;
}
};
配置redurcer(app/app.module.ts)
imports: [
BrowserModule,
RouterModule.forRoot(routes),
StoreModule.forRoot({ modelNum, list}), //配置redurcer
],
第四步:编写services(app/services/ListService.ts)
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {User} from '../models/user';
@Injectable()
export class ListService {
public getList(): Observable<any> {
return this.http.get<{ users: User[] }>('/api/users.json');
}
public removeUser(): Observable<any> {
return Observable.create(function (observer) {
observer.next('true');
});
}
constructor(private http: HttpClient) {
}
}
第五步:编写effects:
import {Injectable} from '@angular/core';
import {Actions, Effect, ofType} from '@ngrx/effects';
import {Observable} from 'rxjs/Observable';
import {Action} from '@ngrx/store';
import {ListActionType, Load, LoadSuccess, RemoveUser, RemoveUserError, RemoveUserSuccess} from '../actions/list';
import {catchError, map, switchMap} from 'rxjs/operators';
import {ListService} from '../services/ListService';
import {of} from 'rxjs/observable/of';
@Injectable()
export class ListEffects {
@Effect()
loadList$: Observable<Action> = this.action$.pipe( //rxjs写法。loadList$是effect名,在外部没有用到,可以随便起。
ofType<Load>(ListActionType.Load),
switchMap(action => {
return this.listService.getList().pipe(map(
users => {
return new LoadSuccess(users);
}
));
})
);
@Effect()
removeUser$: Observable<Action> = this.action$.pipe(
ofType<RemoveUser>(ListActionType.RemoveUser),
switchMap(_ => {
return this.listService.removeUser().pipe(
map(res => {
console.log(res);
if (res === 'true') {
return new RemoveUserSuccess();
} else {
return new RemoveUserError();
}
}),
catchError(err => of(new RemoveUserError()))
);
})
);
constructor(private action$: Actions, private listService: ListService) {
}
}
解析:loadList$ effects 通过actions stream 监听所有的派发的actions,但是使用ofType之后,只对 ListActionType.Load 感兴趣。然后使用switchMap()来将action stream映射到一个新的observable对象。this.listService.getList()会返回一个observable,这个observable会将movies映射到一个新的action new LoadSuccess,若错误发生,返回一个空对象。如果需要进行状态更改,这个action会被派发到store,由reducer处理。
EffectsModule.forRoot( )
必须在AppModule下注册,如果不需要注册任何根级别的Effect,可以Provider一个空数组EffectsModule.forRoot( )
必须在AppModule下注册,如果不需要注册任何根级别的Effect,可以Provider一个空数组
记得在app.module.ts配置effects和HttpClient:
// app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { Action, ActionReducer, combineReducers, StoreModule } from '@ngrx/store';
import { ListEffects } from './effects/list.effects';
imports: [
BrowserModule,
StoreModule.forRoot({modelNum, list}),
EffectsModule.forRoot([ListEffects]),
HttpClientModule
],
// app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { Action, ActionReducer, combineReducers, StoreModule } from '@ngrx/store';
import { ListEffects } from './effects/list.effects';
imports: [
BrowserModule,
StoreModule.forRoot({}),
EffectsModule.forRoot([]),
HttpClientModule
],
//admin.module.ts
import { EffectsModule } from '@ngrx/effects';
import { HttpClientModule } from '@angular/common/http';
import { ListEffects } from './effects/list.effects';
@NgModule({
imports: [
StoreModule.forFeature('common', reducerWrapper),
EffectsModule.forFeature([ ListEffects ]),
HttpClientModule,
],
})
export class MovieModule {}
const combinedReducer: ActionReducer<CommonState, Action> =
combineReducers(COMMON_REDUCERS);
export function reducerWrapper(
state: CommonState, action: Action): CommonState {
return combinedReducer(state, action);
}
//reducers root.ts
export const selectOrderState = Store.createFeatureSelector<CommonState>('common');
通过forRoot 或forFeature 多次运行一个effect class,并不会导致effect会运行多次。forRoot和forFeature的加载效果在功能上是没有区别的,二者的区别在于forRoot设置了effects 所需的providers
第六步:创建组件
ng g component list
第七步、第八步:组件绑定数据模型
组件ts文件:
import {Component, OnInit} from '@angular/core';
import {Store} from '@ngrx/store';
import * as list from '../actions/list';
import {State} from '../reducers/list';
import {User} from '../models/user';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss']
})
export class ListComponent implements OnInit {
public list: User[];
constructor(private store: Store<any>) {
this.store.select('list').subscribe(_list => {
if (_list) {
console.log(_list);
console.log(_list.list);
this.list = _list.list;
}
});
}
removeUser(id) {
console.log(id);
this.store.dispatch(new list.RemoveUser(id));
}
ngOnInit() {
this.store.dispatch(new list.Load());
}
}
组件html文件:
<div>
list
请尝试点击上半部分的li。
<ul>
<li *ngFor="let item of list" (click)="removeUser(item.id)">{{item.name}}</li>
</ul>
<app-list-body></app-list-body>
<br/>
<a routerLink="/model">to counter demo</a>
</div>
最后配置路由:
import {Routes} from '@angular/router';
import {IndexComponent} from './index/index.component';
import {ModelDemoComponent} from './model-demo/model-demo.component';
import {ListComponent} from './list/list.component';
export const routes: Routes = [
{
path: 'list',
component: ListComponent
},
{
path: 'model',
component: ModelDemoComponent
},
{
path: '**',
redirectTo: 'list'
}
];