如何将 ngrx/store 附加到 angular 路由器防护

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,但我还没有真正理解它是如何工作的。

编辑:

在添加一个解析守卫后,returns 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}},
];

我假设 "router guard" 你是指 resolve 守卫?正如您希望在 激活路线之前加载数据?

如果是,那么一切都应该配合得很好:

  • 来自 ngrx/store 的值作为可观察对象公开(例如,在他们的文档中 store.select('counter') 包含一个可观察对象)。
  • 在Angularresolve守卫can return observables.

所以你可以简单地 return ngrx/store 从你的 resolve 中观察到的:

class MyResolver implements Resolve<any> {

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

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

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

将统计信息存储在状态中有什么好处?(与简单地加载它们并在某些组件中显示它们相比)

在 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() 中,我们过滤数据已经存在且不为空(我的业务逻辑的实现细节)。

在 return 为真之后,我们调用 switchMap 到 return 一个 Observable 为真。

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

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

不确定你是否理解,但这是有道理的!

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

随身携带钥匙!

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

你是怎么做到的?

其他答案是设置订阅,但问题是你不想在你的守卫中这样做。订阅 guards 不是好习惯,因为他们需要取消订阅,但如果数据没有解析,或者 guards return 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 将终止订阅。

这也被认为是一个失败的请求:

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; 
    })  
}

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