0

I have an Angular 12 app with a lazy loaded module. The different children routes are protected by some different guards. I can navigate as expected through the app without any problem, but if I directly hit the url of any of the children of the lazy loaded module, then I got in the console the error "Cannot activate an already activated outlet" and the app craches.

I targeted the problem in my guard, if I comment the this.router.navigate(["/shop"]); line of my guard, then I don't get the error (but then I don't have the expected behavior if the guard: it blocks well the page but don't navigate to another url...)

I tried the solution below but it doesn't solve the error (even if it concerns also a lazy loading module, the problem was about named outlets which I don't have) Angular 7 - Multiple outlets : Error: Cannot activate an already activated outlet

Here is my root routing file app.routes.ts with the 2 lazy modules for the whole app, which is imported in the app.module.ts imports by the classic RouterModule.forRoot(APP_ROUTING)`

// file: app.routes.ts
// native modules
import { Routes } from "@angular/router";

export const APP_ROUTING: Routes = [
  // empty url redirects top the shop
  { path: "", redirectTo: "shop", pathMatch: "full" },

  // shop as lazy loaded module
  {
    path: "shop",
    loadChildren: () => import("./shop/shop.module").then(m => m.ShopModule)
  },

  // backoffice as lazy lloaded module
  {
    path: "back",
    loadChildren: () =>
      import("./backoffice/backoffice.module").then(m => m.BackofficeModule)
  },

  // Any non existing url redirects to the shop
  { path: "**", redirectTo: "shop" }
];

Here are the routes of the lazy module:

// shop.routes.ts file
export const SHOP_ROUTES: Routes = [
  {
    path: "",
    component: ShopWorkspaceComponent,

    // prettier-ignore
    children: [
      { path: '', component: HomepageComponent },
      { path: 'connexion', canActivate:[AnonymousGuard], component: ShopLoginComponent },
      { path: 'inscription', canActivate:[AnonymousGuard], component: ShopRegisterComponent },
      { path: 'profil', canActivate:[ClientGuard], component: ShopProfileComponent }
    ]
  }
];

Here is the AnonymousGuard canActivate:

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.authStore.pipe(
      withLatestFrom(this.authStore.pipe(select(isAnonymousSelector))),
      map(([, isAnonymous]) => {
        if (isAnonymous) return true;
        else {
          // if I comment the router.navigate below I don't have the error
          this.router.navigate(["/shop"]);
          return false;
        }
      })
    );
  }
}

and one of the lazy module with the Router.forChild (minus imports):

// shop.module.ts file, lazy loaded    
const COMPONENTS = [
  ShopWorkspaceComponent,
  HomepageComponent,
  ShopMenuComponent,
  ShopRegisterComponent
]

const MODULES = [
  // native modules
  RouterModule.forChild(SHOP_ROUTES),
  BasicModule,

  // feature modules
  ShopLoginModule,
  ShopProfileModule,

];

// Shop module
@NgModule({
  declarations: COMPONENTS,
  imports: MODULES
})
export class ShopModule {}
Thomas Perrin
  • 675
  • 1
  • 8
  • 24
  • Can you show your App.routing file? – Wahab Shah Oct 14 '21 at 09:09
  • I updated my post with full app.routes.ts file which is my root routing file, imported in the app.module.ts imports with the classic `RouterModule.forRoot(APP_ROUTING)` – Thomas Perrin Oct 14 '21 at 13:37
  • okay got it. Show any of your complete child route file? I guess you are missing the `RouterModule.forChild(routes)` in imports array in your feature module route file. Or is it already defined? Kindly check and let me know, because if you are missing that it could be the issue. If it works I will post it as an answer as well then. – Wahab Shah Oct 14 '21 at 13:47
  • Actually it is defined, in the lazy loaded "ShopModule" called in the global routing app.routes.ts file, I have in the @NgModule imports the `RouterModule.forChild(SHOP_ROUTES)`, SHOP_ROUTES being exactly the second file shop.routes.ts I indicated in the post (minus the import). Please notice that I encounter absolutly no issue when I navigate through the app, everything goes as expected. I have the problem only when I directly hit a child url (for example /shop/connexion or /shop/inscription – Thomas Perrin Oct 14 '21 at 16:00
  • I went forward in the investigation: in the canActivate guard, if instead of getting the anonymous value from the ngrx store selector I have a classic test on a boolean value then I have no issue. So definitly the problem comes from the this.authStore.pipe(...) – Thomas Perrin Oct 14 '21 at 18:16

1 Answers1

0

Try updating your canActivate like this:

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.authStore.pipe(
  withLatestFrom(this.authStore.pipe(select(isAnonymousSelector))),
  map(([, isAnonymous]) => {
    if (isAnonymous) return true;
    else {
      return this.router.navigate(["../shop"]);
     }
   })
  );
 }
}
YogendraR
  • 2,144
  • 12
  • 17
  • I got a compiler error with your code, since I return this.router.navigate wich returns a Promise inside the map of the pipe, the compiler sees as return type of the canActivate the type "Observable>", so its rejects code as it is not the signature of the canActivate – Thomas Perrin Oct 14 '21 at 13:29
  • updated the return type – YogendraR Oct 14 '21 at 13:33
  • Yes I also tried that one, but that still not works because the `return this.router.navigate` is done in the pipe of the store. If I wasn't querying the ngrx store with authStore.pipe and directly returning this.router.navigate it would be ok, but in the pipe of the store I can't do that because the returned signature is `Observable>` with the Promise inside of the Observable, instead of `Observable | Promise`. Maybe the solution is to find a way to put the logic outside of the store query? (but still querying the store to get the isAnonymous status) – Thomas Perrin Oct 14 '21 at 13:42
  • I am going forward in the investigation: in the canActivate guard, if instead of getting the anonymous value from the ngrx store selector I have a classic test on a boolean value then I have no issue. So definitly the problem comes from the `this.authStore.pipe(...)` – Thomas Perrin Oct 14 '21 at 17:29