3

I'm trying to build a sequence of RXJS commands in a guard, in order to achieve this result in a simple library (as in books) application :

  • Check in the Store
  • If the state is not loaded, trigger an action
  • Once it's loaded, filter the data to find one specific item
  • If you find it, allow access to page, if not, send to 404

I've been struggling for a while and looking online does not give an answer according to version 6 of Angular, or standards... What I've succeeded into doing what to create much of it if the store is loaded before hitting the guard, but if it needs to fetch the data, it loops indefinitely.

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

Thanks a lot!

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
jprivard
  • 49
  • 1
  • 3

1 Answers1

4

The problem is in this section:

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

As soon as you dispatch the Load action, most likely your reducer sets the loaded property to false, re-triggering the initial selector, which again triggers the dispatch of the action.

The way I see it, what you need to do is:

  1. Check only once if the list is loaded. If its not, dispatch an action. Otherwise NOOP.
  2. As soon as the previous step is completed, wait for the list to be loaded and check once for the existence of the entity.

This could be archived through the following:

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

Let me know if this helps.

matt_lethargic
  • 2,706
  • 1
  • 18
  • 33
Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73