15

我是 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}},
];
4

6 回答 6

12

我不太明白您为什么要通过解析器将解析的数据发送到您的组件。设置 ngrx 存储的全部意义在于拥有单一数据源。因此,您只想在这里做的是确保数据是真实的。休息,组件可以使用选择器从存储中获取数据。

我可以看到您是LOAD_ACTIONngOnInit组件的方法中调用的。你不能调用一个动作来解析数据,然后它会导致Init你调用动作的组件!这就是您的路由器不加载路由的原因。

不知道你是否理解,但它是有道理的!

简单来说,你正在做的就是这个。您正在锁定一个房间,然后将钥匙从门下方的缝隙中推开,然后想知道为什么门打不开。

随身携带钥匙!

守卫的 resolve 方法应该调用LOAD_ACTION. 然后解析应该等待数据加载,然后路由器应该继续处理组件。

你是怎样做的?

其他答案是设置订阅,但问题是你不想在你的警卫中这样做。订阅守卫不是一个好习惯,因为他们需要被取消订阅,但是如果数据没有被解析,或者守卫返回 false,那么路由器永远不会取消订阅,我们就会一团糟。

所以,使用take(n). 此运算符将从n订阅中获取值,然后自动终止订阅。

此外,在您的操作中,您将需要LOAD_STATISTICS_SUCCESS一个LOAD_STATISTICS_FAIL. 因为您的服务方法可能会失败!

在 reducerState中,您需要一个loaded属性,该属性会true在服务调用成功和LOAD_STATISTICS_SUCCESS操作被调用时转向。

在主减速器中添加一个选择器,getStatisticsLoaded然后在您的 gaurd 中,您的设置将如下所示:

resolve(): Observable<boolean> {

this.store.dispatch({type: LOAD_STATISTICS});

return this.store.select(getStatisticsLoaded)
    .filter(loaded => loaded)
    .take(1);

}

因此,只有当加载的属性更改为 true 时,过滤器才会允许流继续。take将采用第一个值并传递。此时解析完成,路由器可以继续。

再次,take将终止订阅。

于 2017-04-01T14:16:19.437 回答
8

在 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。

于 2017-02-28T16:02:32.863 回答
4

我假设“路由器守卫”是指resolve守卫?就像您希望在激活路线之前加载数据一样?

如果是,那么一切都应该很好地结合在一起:

  • 来自ngrx/store 的值作为可观察对象公开(例如,在他们的文档store.select('counter')中包含可观察对象)。
  • 在 Angular中,resolve守卫可以返回 observables

因此,您可以简单地从您的解析中返回 ngrx/store 可观察对象:

class MyResolver implements Resolve<any> {

  constructor(private store: Store<any>) {}

  resolve(): Observable<any> {
    // Adapt code to your situation...
    return this.store.select('statistics');
  }
}

话虽如此,您的设置确实有点复杂。我倾向于将状态视为瞬态数据(菜单的折叠状态,在会话期间需要在多个屏幕/组件之间共享的数据,例如当前用户),但我不确定我是否会加载大量数据从后端进入状态。

将统计信息存储在状态中可以得到什么?(而不是简单地加载它们并在某些组件中显示它们)

于 2017-02-28T12:26:12.317 回答
3

如果加载不成功,那么您可能想取消导航。可以通过抛出错误来完成。最重要的是 - 返回的 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;
        })
    );
}
于 2018-05-23T00:13:36.303 回答
2

这也考虑了一个失败的请求:

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);
  }
于 2017-07-29T20:48:00.450 回答
1

这个答案适用于在这种情况下使用 ngrx/effect 感到沮丧的人。最好只使用没有ngrx/效果的简单方法。

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; 
    })  
}
于 2017-09-13T15:40:41.080 回答