3
votes

Trying to stop access to a few routes if the user directly accesses them using a bookmark, or by manually entering the URL. The routes are a set of chained routes in a specific order. For example, like a wizard.

In the past I didn't have any issues using canActivate and using a service, which if it didn't have the proper data indicating the previous route states were accessed (ie. page 1 before page 2 etc) it would route the user to the initial route (page 1), but after using enableTracing and testing this on a couple quickly generated components it seems that as the application is loading and accesses a CanActivate guard that prevents access to the route, that invoking this.router.navigate(['/example/route/path']); doesn't seem to make it to its destination (page 1) if page 2 or more is accessed directly.

Putting a TestComponent that isn't lazy loaded in the AppRoutingModule or using the top-level not-found route works, but absolutely nothing in the MainModule is routeable. So it seems to be potentially related to lazy loaded modules that contain lazy loaded modules.

Anyone found a way around this? Or is this just another issue related to lazy loading in Angular like entryComponents - https://stackoverflow.com/a/51689994/1148107.

I'm not sure why the application was set up this way so worst case I'll try and push for a quick refactor and flatten the lazy loading of feature modules.

CanActivate

canActivate(
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
  if (this.storeService.hasAccess) {
    return true;
  }

  this.router.navigate(['/store/page/1']); // Doesn't route to page 1
  return false;
}

AppRoutingModule Routes

const routes: Routes = [
  {
    path: '',
    loadChildren: './main/main.module#MainModule',
    canActivate: [AuthGuard]
  },
  // ...
  {
    path: 'not-found',
    loadChildren: './not-found/not-found.module#NotFoundModule'
  },
  {
    path: '**',
    redirectTo: 'not-found'
  }
];

MainRoutingModule Routes

const routes: Routes = [
  {
    path: '', component: MainComponent,
    children: [
      {
        path: '',
        redirectTo: 'store',
        pathMatch: 'full'
      },
      {
        path: 'store',
        loadChildren: './store/store.module#StoreModule'
      },
      // ...
    ]
  }
];

StoreRoutingModule

const routes: Routes = [
  {
    path: '',
    children: [
      {
        path: 'page/1',
        component: Page1Component,
        data: {
          title: 'Store'
        }
      },
      {
        path: 'page/2',
        component: Page2Component,
        // Redirects to a blank page instead of page 1 when directly 
        // accessed by a browser bookmark
        canActivate: [CanActivateStoreGuard], 
        data: {
          title: 'Store'
        }
      },
      // ...
    ]
  }
];
1
Can you add the route configuration and how you're using canActivate. - Reactgular
isn't this the use case for canLoad? angular.io/guide/… - bryan60
Hi @cgTag I've added a code example - mtpultz
Hi @bryan60, they should be able to access the module, but not the page 2 route component if the service indicates they haven't visited page 1. Which works if you try and route directly to page 2, but fails if they manually enter the URL to access page 2. - mtpultz
OK I get it now, your problem is that you have different copies of the service due to a module being lazy loaded. You need to create a core module and provide your service there to ensure it is a singleton. angular.io/guide/… - bryan60

1 Answers

1
votes

You need to perform the route change inside the observable that is subscribed to by the Angular router.

public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
   return new Observable((subscriber) => {
       if (this.storeService.hasAccess) {
          subscriber.next(true);
       } else {
          this.router.navigate(['/store/page/1']);
          subscriber.next(false);
       }
       subscriber.complete();
   });
} 

I can not verify if this will resolve your issue, but I remember running into this issue. When I look at my guards this is what I do.

Updated:

If the above doesn't work (I have my doubts), then you could try wrapping the route change in a setTimeout.

canActivate(next: ActivatedRouteSnapshot,  state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
 if (this.storeService.hasAccess) {
    return true;
 }

 window.setTimeout(()=> this.router.navigate(['/store/page/1']));

 return false;
}