@ngrx/store 如何确保reducer中的state改变之后再发送请求?
应用场景
一个手机应用,在启动的时候会去检查用户是否在之前已经登陆过(在本地存储或者IndexedDB中检查),
如果已经登陆则使用登陆信息(accessToken)去获取数据,如果没有登陆, 则不带登陆信息获取数据。
模块
- 前端框架:angular4
- UI框架:ionic3
- 状态管理:@ngrx系列全家桶(@ngrx/store 4.0、@ngrx/effects 4.0、@ngrx/db 2.0)
Action
app.action.ts
import { Action } from '@ngrx/store';
import { User } from '../models/user';
// 应用在启动的时候触发的Action名称
export const APP_INIT = '[App] Initial';
// 应用启动触发Action成功
export const APP_INIT_SUCCESS = '[App] Initial Success';
// 应用在启动的时候触发的Action模型
export class AppInitialAction implements Action{
readonly type = APP_INIT;
constructor() { }
}
// 应用启动触发Action成功的模型
export class AppInitialSuccessAction implements Action {
readonly type = APP_INIT_SUCCESS;
// 构造中的参数为User对象,由于用户可能没有登录, 可能为undefined或者null
constructor(public payload: User) { }
}
export type Actions = AppInitialAction | AppInitialSuccessAction;
Reducer
user.reducer.ts 中只列出关键部分.
import { User } from '../models/user';
import * as app from '../actions/app.action';
// 状态模型定义
export interface State {
isFetching: boolean; // 是否正在发送请求
checked: boolean; // checked用于标识是否已经进行了用户登录检查
error: boolean; // 是否发生错误
message: string;// 错误/成功消息
user: User; // 当前登录用户模型,其中包含accessToken
}
// 默认状态
const initialState: State = {
isFetching: false, //未发送请求
checked: false, // 未进行检查
error: false, //没有错误发生
message: undefined, // 没有消息
user: undefined // 用户没有登录
}
export const reducer = function (state: State = initialState, action: app.Actions): State {
switch (action.type) {
case app.APP_INIT_SUCCESS: // 检查用户登录完成
// 由于用户可能在之前未进行登录,所以返回的可能是undefined或者null
// 如果以下条件不成立,则说明用户之前已经登录过。
if (action.payload != undefined && action.payload != null) {
return Object.assign({}, state, {
checked: true,
user: action.payload
});
} else {
// 即使用户未登录,也需要保存已经检查过是否登录,避免重复检查。
return Object.assign({},state,{ checked:true });
}
default:
return state;
}
}
Effects
下面是判断用户登录的关键部分, 经过调试和跟踪,能够正常运行。
// 在初始化应用的action中判断用户是否登录
@Effect() init$$: Observable<Action> = this.actions$
.ofType(app.APP_INIT)
.switchMap(() =>
this.db.query('user').map((u: User) => u).toArray()
.map((users: User[]) => {
const x = new app.AppInitialSuccessAction({
id: '11111',
loginname: 'ycpaladin',
avatar_url: 'https://avatars3.githubusercontent.com/u/3337028?v=4&s=120',
accessToken: 'xxxx-zzzz-xxxx-cccc'
});
return x;
}));
// 自动触发初始化应用的请求
@Effect() init$ = defer(() => {
return of(new app.AppInitialAction());
// this.store.dispatch(new app.AppInitialAction());
});
Component
data: Observable<Topic[]>;
checked: Observable<boolean>;
@Input() tabName: string;
constructor(private navCtrl: NavController, private store: Store<fromRoot.State>) {
this.data = this.store.select(fromRoot.getTopics); // 对应到数据请求的结果
this.checked = this.store.select(fromRoot.getChecked); // 对应user.reducer中的checked, 其默认值一定是false
}
//
ngOnInit():void{
// 使用zip操作符监视2个属性
zip(this.checked, this.data)
// 主要判断this.checked中的value是否为true
.filter(([checked , data]) => {
return checked === true && data.length === 0;
})
.subscribe(([checkedUser, data]) => {
// 获取数据
this.store.dispatch(new topic.LoadAction({ tabName: this.tabName, pageIndex: 1 }));
});
}
问题
在Component中的ngOnInit方法中这些出现了问题, 默认他不会去获取数据。 我跟踪了一下,还是在filter方法中checked的值为false导致的。
实际上在fliter方法上打上断点等待一定的时间之后,里面的条件是成立的,结果就是数据按预期的样子出现在页面中了,但是这种行为是不正确的。
我觉着这其实是一个先后顺序的问题,所以有没有办法确保checked的值为true,之后再去请求数据?
另外,我认为redux中应该也会遇到这样的场景,应该怎样解决呢?
临时解决方案
目前我只想到一种办法,在ngOnInit中使用setInterval去轮询:
const timer = setInterval(() => {
const x = zip(this.checkedUser, this.data)
.filter(([checkedUser, data]) => {
return checkedUser === true && data.length === 0;
})
.subscribe(([checkedUser, data]) => {
this.store.dispatch(new topic.LoadAction({ tabName: this.tabName, pageIndex: 1 }));
if (x !== undefined){
x.unsubscribe();
}
clearInterval(timer);
});
}, 0);
还有更好的办法吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
this.data = this.store.select(fromRoot.getTopics),不是很清楚这个究竟是什么数据的结果。
但从你的问题描述‘在fliter方法上打上断点等待一定的时间之后,里面的条件是成立的’来看,我猜是zip操作符的问题,zip流弹珠模型如下:
建议用withLatestFrom