3

我正在尝试在警卫中构建一系列 RXJS 命令,以便在一个简单的库(如书籍)应用程序中实现此结果:

  • 在商店中检查
  • 如果状态没有加载,触发一个动作
  • 加载后,过滤数据以找到一个特定项目
  • 如果找到,允许访问页面,如果没有,发送到 404

我已经苦苦挣扎了一段时间,并且根据 Angular 6 版或标准,在网上查看并没有给出答案......如果商店在击中警卫之前加载,我已经成功地做了什么来创建它,但如果它需要获取数据,它会无限循环。

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
  return this.store.pipe(
    select(state => state.books),
    tap(state => {
      if (!state.loaded) {
        this.store.dispatch(new BooksActions.Load());
      }
    }),
    filter(state => state.loaded),
    take(1),
    map(state => state.list.find(book => book.id === route.params['id'])),
    map((book) => {
      if(!!book) return true;
      this.router.navigate(['/404']);
      return false;
    })
  );
}

非常感谢!

4

1 回答 1

4

问题出在本节:

select(state => state.books),
    tap(state => {
      if (!state.loaded) {
        this.store.dispatch(new BooksActions.Load());
      }
    }),   

一旦你分派了 Load 动作,你的 reducer 很可能将加载的属性设置为 false,重新触发初始选择器,这再次触发动作的分派。

在我看来,你需要做的是:

  1. 如果列表已加载,则仅检查一次。如果不是,则派发一个动作。否则 NOOP。
  2. 上一步完成后,等待列表加载并检查一次实体是否存在。

这可以通过以下方式存档:

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> 
{
   return concat(this.loadIfRequired(), this.hasBookInStore(route.params['id'])).pipe(skip(1));
}

private loadIfRequired(): Observable<any>
{
   return booksState$.pipe(
    map(state => state.loaded),
    take(1),
    tap(loaded=> {
      if (!loaded) {
        this.store.dispatch(new BooksActions.Load());
      }
    }));
}

private hasBookInStore(id: string): Observable<boolean>
{
  return this.booksState$.pipe(
    first(state => state.loaded),
    map(state => state.list.find(book => book.id === id)),
    map((book) => {
     if(!!book) return true;
     this.router.navigate(['/404']);
     return false;
   })
  );
}

private get booksState$(): Observable<BooksState>
{
 return this.store.pipe(
    select(state => state.books));
}

让我知道这是否有帮助。

于 2018-07-19T20:59:17.883 回答