[英]How to attach ngrx/store with an angular router guard
我是 ngrx/store 的初学者,这是我使用它的第一个项目。
我已经成功地使用 ngrx/store 设置了我的 angular 项目,并且在初始化我的主要组件后,我能够分派加载操作,如下所示:
ngOnInit() { this.store.dispatch({type: LOAD_STATISTICS}); }
我已经设置了一个效果来在调度此操作时加载数据:
@Effect()
loadStatistic = this.actions.ofType(LOAD_STATISTICS).switchMap(() => {
return this.myService.loadStatistics().map(response => {
return {type: NEW_STATISTICS, payload: response};
});
});
我的减速器看起来像这样:
reduce(oldstate: Statistics = initialStatistics, action: Action): Statistic {
switch (action.type) {
case LOAD_STATISTICS:
return oldstate;
case NEW_STATISTICS:
const newstate = new Statistics(action.payload);
return newstate;
....
虽然这有效,但我无法理解如何将它与路由器防护一起使用,因为我目前只需要调度 LOAD_ACTION 一次。
此外,组件必须调度 LOAD 操作来加载初始数据对我来说听起来不合适。 我希望商店本身知道它需要加载数据,而我不必先分派动作。 如果是这种情况,我可以删除组件中的 ngOnInit() 方法。
我已经研究过 ngrx-example-app 但我还没有真正理解它是如何工作的。
编辑:
添加返回 ngrx-store observable 的解析保护后,路由不会被激活。 这是解决方案:
@Injectable()
export class StatisticsResolver implements Resolve<Statistic> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Statistic> {
// return Observable.of(new Statistic());
return this.store.select("statistic").map(statistic => {
console.log("stats", statistic);
return statistic;
});
}
这是路线:
const routes: Routes = [
{path: '', component: TelefonanlageComponent, resolve: {statistic: TelefonieStatisticResolver}},
];
我不太明白为什么您要通过解析器将解析后的数据发送到您的组件。 设置 ngrx 存储的全部意义在于拥有单一的数据源。 因此,您在这里只想做的是确保数据是真实的。 休息,组件可以使用选择器从存储中获取数据。
我可以看到您正在从组件的ngOnInit
方法中调用LOAD_ACTION
。 你不能调用一个动作来解析数据,这会导致你在Init
上调用该动作的组件! 这就是你的路由器没有加载路由的原因。
不知道你是否理解,但这是有道理的!
简单来说,你正在做的就是这个。 您正在锁定房间,然后将钥匙从门下方的缝隙中推开,然后想知道为什么门没有打开。
随身携带钥匙!
守卫的解析方法应该调用LOAD_ACTION
。 然后解析应该等待数据加载,然后路由器应该继续组件。
你是怎样做的?
其他答案是设置订阅,但问题是您不想在您的警卫中这样做。 订阅守卫不是一个好习惯,因为他们需要被取消订阅,但是如果数据没有得到解析,或者守卫返回 false,那么路由器永远不会取消订阅,我们就会一团糟。
所以,使用take(n)
。 此运算符将从订阅中获取n
值,然后自动终止订阅。
此外,在您的操作中,您将需要LOAD_STATISTICS_SUCCESS
和LOAD_STATISTICS_FAIL
。 因为您的服务方法可能会失败!
在 reducer State
,您需要一个loaded
属性,当服务调用成功并调用LOAD_STATISTICS_SUCCESS
操作时,该属性变为true
。
在主减速器中添加一个选择器, getStatisticsLoaded
,然后在您的 gaurd 中,您的设置将如下所示:
resolve(): Observable<boolean> {
this.store.dispatch({type: LOAD_STATISTICS});
return this.store.select(getStatisticsLoaded)
.filter(loaded => loaded)
.take(1);
}
因此,只有当加载的属性更改为 true 时,过滤器才会允许流继续。 take
将取第一个值并传递。 此时解析完成,路由器可以继续。
同样, take
将终止订阅。
我刚刚在 AngularFrance 提供宝贵意见后自己解决了这个问题。 由于我仍然是初学者,我不知道这是否应该这样做,但它确实有效。
我实现了这样的 CanActivate Guard:
@Injectable()
export class TelefonieStatisticGuard implements CanActivate {
constructor(private store: Store<AppState>) {}
waitForDataToLoad(): Observable<Statistic> {
return this.store.select(state => state.statistic)
.filter(statistic => statistic && !statistic.empty);
}
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
this.store.dispatch({type: LOAD_STATISTIC});
return this.waitForDataToLoad()
.switchMap(() => {
return Observable.of(true);
});
}
}
}
canActivate(...) 方法首先调度一个动作来加载数据。 在 waitForDataToLoad() 中,我们过滤数据已经存在并且不为空(我的业务逻辑的实现细节)。
在返回 true 之后,我们调用 switchMap 返回一个为 true 的 Observable。
我假设“路由器防护”是指resolve
防护? 就像您希望在激活路线之前加载数据一样?
如果是,那么一切都应该很好地协同工作:
store.select('counter')
包含一个 observable)。resolve
守卫中可以返回 observables 。所以你可以简单地从你的解析中返回 ngrx/store observable:
class MyResolver implements Resolve<any> {
constructor(private store: Store<any>) {}
resolve(): Observable<any> {
// Adapt code to your situation...
return this.store.select('statistics');
}
}
话虽如此,您的设置似乎确实有点复杂。 我倾向于将状态视为瞬态数据(菜单的折叠状态,在会话期间需要在多个屏幕/组件之间共享的数据,例如当前用户),但我不确定我会加载从后端到状态的大片数据。
您从将统计信息存储在状态中获得什么? (相对于简单地加载它们并在某个组件中显示它们)
如果加载不成功,那么您可能想取消导航。 可以通过抛出错误来完成。 最重要的是 - 返回的 observable 必须完成或以错误结束。
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
this.store.dispatch(new LoadStatistic(route.params.someParam));
return this.store.pipe(select(selectStatisticModel),
filter(s => !s.isLoading),
take(1),
map(s => {
if (!s.isLoadSuccess) {
throw s.error;
}
return true;
})
);
}
这也考虑了失败的请求:
canActivate(): Observable<boolean> {
this.store.dispatch({type: LOAD_STATISTICS);
return this.store.select((state) => state.statistics)
.filter((statistics) => statistics.loaded || statistics.loadingFailed)
.map((statistics) => statistics.loaded);
}
这个答案适用于在这种情况下使用 ngrx/effect 感到沮丧的人。 也许最好使用没有 ngrx/effects 的简单方法。
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
// this will only dispatch actions to maybe clean up, NO EFFECTS
this.store.dispatch({type: LOAD_STATISTIK});
return this.service.loadOne()
.do((data) => {
this.store.dispatch({type: LoadSuccess, payload: data })
}).map(() => {
return true;
})
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.