Search code examples
angularrxjsangular2-routingngrx

Route Guard loading ngrx store data only navigates to protected route after second click


To load data from the backend into my ngrx store before loading the actual component, I use a route guard in the canActivate attribute of the route.

{path: 'my-route', component: MyRouteComponent, canActivate: [LoadDataGuardService]}

The Guard should check in the store if the corresponding data is already available. If not, it should be loaded from the backend into the store. As soon as the data is in the store, the route should be opened (with the first, initial click). Here is the canActivate method of my Guard.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const loadedTasks = this.store.pipe(select(selectTaskMap))
      .pipe(map(taskMap => Object.keys(taskMap).length > 0));

    loadedTasks
      .pipe(
        take(1),
        filter(loaded => !loaded),
        map(() => this.store.dispatch(GetTasks())))
      .subscribe(() => this.store);

    return loadedTasks
      .pipe(take(1));
  }

Loading the data into the store works on the first click. But I have to click a second time on the link to be navigated to the desired view, if the data has to be loaded. It seems that the first click only loads the data but does not return an Observable of True after sucessfully loading the data.

How do I adjust the code to load the data into the store and open the route as soon as the data is loaded in the store on the first click?

EDIT:

Here is a working example on stackblitz for reproduction: Stackblitz Link
You can clearly see, that the content of the route componetn is just shown after the second click on the Activate Route link.
In order to clear the store while testing I added a button.


Solution

  • Looks like the problem is that first time the tasks weren't loaded yet.

    Try to move first after filter.

    import {Injectable} from '@angular/core';
    import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
    import {Store, select } from '@ngrx/store';
    import {filter, tap, map, take} from 'rxjs/operators';
    import { selectEntityMap } from './store/selectors/entity.selectors';
    import { AppState } from './store/state/app.state';
    import { GetEntity } from './store/actions/entity.actions';
    
    @Injectable({
      providedIn: 'root'
    })
    export class LoadDataGuardService implements CanActivate {
    
      constructor(public router: Router, private store: Store<AppState>) {
      }
    
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        return this.store.pipe(select(selectEntityMap)).pipe(
          map(taskMap => Object.keys(taskMap).length > 0),
           tap(loaded => {
            if (!loaded) { // checking if we need to dispatch the action.
              this.store.dispatch(GetEntity());
            }
          }),
          filter(loaded => !!loaded), // waiting until the tasks have been loaded.
          take(1),
        );
      }
    }