我已经构建了一个Store
界面:
export interface IStore {
user: IUser;
}
其中IUser
是:
export interface IUser {
id: string;
name: string;
username: string;
customer: string;
}
在我的组件中,我创建了对IStore.user
的订阅:
export class LoginComponent implements OnInit {
private user$: Observable<IUser>;
private userSub: Subscription;
constructor(private store$: Store<IStore>)
{
this.user$ = this.store$.select(state => state.user);
}
ngOnInit():void {
this.userSub = this.user$.subscribe(
(user: IUser) => {
this.router.navigate(['/app']); ((((1))))
},
(error: any) => {
this.addAlert(error.message);
}
);
}
ngOnDestroy():void {
this.userSub.unsubscribe();
}
public login():void {
this.store$.dispatch({ type: 'USER_REDUCER_USER_LOGIN' });
}
}
目前,已达到((((1))))
代码,仅建立了inmediatly订阅。然而,当((((1)))) callback
login()`方法。
'USER_REDUCER_USER_LOGIN' action is dispatched in
这是我的UserReducer.ts
:
export class UserReducer {
private static reducerName = 'USER_REDUCER';
public static reducer(user = initialUserState(), {type, payload}: Action) {
if (typeof UserReducer.mapActionsToMethod[type] === 'undefined') {
return user;
}
return UserReducer.mapActionsToMethod[type](user, type, payload);
}
// tslint:disable-next-line:member-ordering
/**
* Default reducer type. I want all sources.
*/
public static USER_LOGIN = `${UserReducer.reducerName}_USER_LOGIN`;
/**
* User login success.
*/
public static USER_LOGIN_SUCCESS = `${UserReducer.reducerName}_USER_LOGIN_SUCCESS`;
private static userLoginSuccess(sourcesRdx, type, payload) {
return Object.assign(<IUser>{}, sourcesRdx, payload);
}
/**
* User login fails.
*/
public static USER_LOGIN_FAILED = `${UserReducer.reducerName}_USER_LOGIN_FAILED`;
private static userLoginFailed(sourcesRdx, type, payload) {
return Object.assign(<IUser>{}, sourcesRdx, payload);
}
// ---------------------------------------------------------------
// tslint:disable-next-line:member-ordering
private static mapActionsToMethod = {
[UserReducer.USER_LOGIN_SUCCESS]: UserReducer.userLoginSuccess,
[UserReducer.USER_LOGIN_FAILED]: UserReducer.userLoginFailed,
};
}
和initialUserState()
是:
export function initialUserState(): IUser {
return {
id: '',
name: '',
username: '',
customer: ''
};
};
有什么想法吗?
修改
@Injectable()
export class UserEffects {
constructor(
private _actions$: Actions,
private _store$: Store<IStore>,
private _userService: UsersService,
) { }
@Effect({ dispatch: true }) userLogin$: Observable<Action> = this._actions$
.ofType('USER_REDUCER_USER_LOGIN')
.switchMap((action: Action) =>
this._userService.checkPasswd(action.payload.username, action.payload.password)
.map((user: any) => {
return { type: 'USER_REDUCER_USER_LOGIN_SUCCESS', payload: user };
})
.catch((err) => {
return Observable.of({
type: 'USER_REDUCER_USER_LOGIN_ERROR',
payload: { error: err }
});
})
);
}
答案 0 :(得分:1)
您所看到的是预期的行为。当您订阅商店时,它会立即发出当前状态,如果尚未收到操作,则该状态将是初始状态。
最简单的解决方案是使用filter
运算符:
import 'rxjs/add/operator/filter';
ngOnInit():void {
this.userSub = this.user$
.filter(Boolean)
.subscribe(
(user: IUser) => { this.router.navigate(['/app']); },
(error: any) => { this.addAlert(error.message); }
);
}
应用过滤器后,user$
观察者不会发出空值,因此导航不会发生,直到用户登录为止。
您可以使用first
运算符进一步简化实施。当观察完成或错误时,订阅者将自动取消订阅。通过使用first
运算符,组合的observable将在第一次发射后完成,因此不需要手动取消订阅,并且可以删除userSub
属性和ngOnDestroy
方法:
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
ngOnInit(): void {
this.user$
.filter(Boolean)
.first()
.subscribe(
(user: IUser) => { this.router.navigate(['/app']); },
(error: any) => { this.addAlert(error.message); }
);
}
另一种解决方案是使用@ngrx/effects
来管理路由。您可能认为LoginComponent
的责任是获取凭据并对用户进行身份验证,但可能不是该组件负责决定导航的位置。如果您想要其他东西负责导航,您可以创建一个侦听USER_REDUCER_USER_LOGIN_SUCCESS
的路由效果。
您可能还需要考虑使用@ngrx/router-store
在商店中包含路由器状态 - 以便于进行时间旅行调试和撤消导航更改。有关详细信息,请参阅this answer。