Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check for existence of element in possibly to be loaded list in ngrx

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!

like image 869
jprivard Avatar asked Oct 31 '25 09:10

jprivard


1 Answers

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.

like image 148
Jota.Toledo Avatar answered Nov 02 '25 00:11

Jota.Toledo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!