17

I have an application, which need to separate authenticated and guest users components. But I need, that both components will be loaded by '/' route. I wrote

{
    path: 'desktop',
    loadChildren: 'app/member/member.module#MemberModule',
    canActivate: [LoggedInGuard],
},
{
    path: '',
    loadChildren: 'app/guest/guest.module#GuestModule',
    canActivate: [GuestGuard],
},

And it works. But how to make, that both component load by same url? I had tried to write path: '' for Member's module route, but the second router rule is not performed. Here are guards code:

LoggedInGuard:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if(this.sessionService.isLoggedIn()) {
        return true;
    } else {
        return false;
    }
}

GuestGuard:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if(!this.sessionService.isLoggedIn()) {
        return true;
    } else {
        return false;
    }
}

Here is a plunker: http://embed.plnkr.co/VaiibEVGE79QU8toWSg6/

How should I do it properly? Thank you

Max Link
  • 185
  • 1
  • 1
  • 6

4 Answers4

17

So i was finally able to do this. The thing is Angular uses first match policy, so we need to match routes in a guard-type way, to be sure that right route with right module will be matched.

First thing we need to add custom matchers for our routes which will only match them on conditions that we want (user type for example).

{
 path: 'samePath',
 matcher: firstMatcher,
 loadChildren: '../first/first.module#FirstModule'
},
{
 path: 'samePath',
 matcher: secondMatcher,
 loadChildren: '../second/second.module#SecondModule'
}

And matchers code is something like this: In here i injected AuthService service from AppModule, and checked users type with it. So routes can be matched according to users type.

import { applicationInjector } from '../../main';

export function firstMatcher (url: UrlSegment[]) {
  const auth =  applicationInjector.get(AuthService);
  return auth.isUserType('admin') ? ({consumed: [url[0]]}) : null;
}

And now only thing we need is to create applicationInjector in our main module, so we could inject service in our matcher-function;

export let applicationInjector: Injector;

platformBrowserDynamic().bootstrapModule(AppModule).then((componentRef) => {
  applicationInjector = componentRef.injector;
})
Anton Stepanenkov
  • 1,026
  • 8
  • 15
  • 2
    I would like to add that `path` must not be specified when using matchers since it gives out a compiler error; it would be redundant anyway since all path information that would be otherwise received via `path` is passed via the `url` parameter of the matcher. This answer just saved my day! – Sofia Paixão Mar 21 '19 at 18:51
  • You'll get circular dependency injecting service into main.ts. – Mukus Apr 02 '19 at 08:01
  • Anton Stepanenkov, could you please explain this example in Angular 8? Your example is not working, applicationInjector returns undefined... – mait.taim Oct 14 '19 at 13:44
  • I would advise to use @German Quinteros response, because here we expose root injector to global scope. – Imants Volkovs Feb 07 '22 at 14:40
11

You can use a Module that handles what module should be load by providing the ROUTES of the RouterModule using the useFactory provider of Angular.

The code could be something like that.

// HandlerModule

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    RouterModule
  ],
  providers: [
    {
      provide: ROUTES,
      useFactory: configHandlerRoutes,
      deps: [SessionService],
      multi: true
    }
  ]
})


export class HandlerModule {}

export function configHandlerRoutes(sessionService: SessionService) {
  let routes: Routes = [];
  if (sessionService.isLoggedIn()) {
    routes = [
      {
        path: '', loadChildren: () => import('app/member/member.module').then(mod => mod.MemberModule)
      }
    ];
  } else {
    routes = [
      {
        path: '', loadChildren: () => import(app/guest/guest.module).then(mod => mod.GuestModule)
      }
    ];
  }
  return routes;
}

Then in your AppRoutingModule the module of the path '' is going to be the HandlerModule:

// AppRoutingModule

 {
    path: '',
    loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)
}

After in the SessionService you have to update the Router.config when the value that provides the method isLoggedIn changes, because the application will only load the page (module) that had loaded the first time. This is because the function “configHandlerRoutes” use by the useFactory provider in the HandlerModule is only executed the first time we navigate to the “” path and after that the Angular Router already know which module he has to load.

In conclusion in the SessionService you have to do:

  export class SessionService {
  private loggedIn: boolean;
  constructor(private router: Router) {
    this.loggedIn = false;
  }

  public isLoggedIn(): boolean {
    return this.loggedIn;
  }

  public setLoggedIn(value: boolean): void {
    const previous = this.loggedIn;
    this.loggedIn = value;
    if (previous === this.loggedIn) {
      return;
    }
    const i = this.router.config.findIndex(x => x.path === '');
    this.router.config.splice(i, 1);
    this.router.config.push(
      {path: '', loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)}
    );
  }
}

That's it.

If you want another reference here is an article where they use the same approach: https://medium.com/@german.quinteros/angular-use-the-same-route-path-for-different-modules-or-components-11db75cac455

German Quinteros
  • 1,870
  • 9
  • 33
  • great flexible solution!! – Igor Kurkov Jan 20 '20 at 08:19
  • but dont know when I start Application it doesn't work I have to do some change in that module then only it start to run – Mustafa Kunwa Apr 10 '20 at 05:41
  • Facing this error "ERROR in Cannot read property 'loadChildren' of undefined" in generation AOT build – HV Sharma Jul 31 '20 at 12:43
  • Yes, that is an issue with the Angular 8 version. It's working fine in Angular version 9+, check that the error was already reported and resolved in the repo: https://github.com/gquinteros93/same-path-for-different-modules/issues/1 – German Quinteros Jul 31 '20 at 12:49
  • This is the best solution for my scenario (switching between old & new versions of a page with a feature flag). One thing that threw me for a loop though: the `path` of the routes in the `HandlerModule` (`path: ''`) are NOT tied to the path in the `AppRoutingModule`. For my scenario, the path in the `AppRoutingModule` was not an empty string. But in `HandlerModule` they should remain as empty strings. – Russ Sep 08 '21 at 19:40
-3

One way to do this will be to route to appropriate area depending on whether the user is logged in or not.

(i.e. whether you open blank route or guest route, it will get redirected appropriately, and back button won't work)

Routes:

{
    path: '',
    loadChildren: 'app/member/member.module#MemberModule',
    canActivate: [LoggedInGuard],
},
{
    path: 'guest',
    loadChildren: 'app/guest/guest.module#GuestModule',
    canActivate: [GuestGuard],
}

LoggedInGuard::

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if(this.sessionService.isLoggedIn()) {
        return true;
    } else {
        // route to 'guest' if not logged in
        this.router.navigate(['/guest'], { replaceUrl: true });
        return false;
    }
}

GuestGuard (with automatic route to MemberComponent if logged in):

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if(this.sessionService.isLoggedIn()) {
        // route to member area if already logged in
        this.router.navigate(['/'], { replaceUrl: true });
        return false;
    } else {
        return true;
    }
}
Santanu Biswas
  • 4,699
  • 2
  • 22
  • 21
  • OP wants both of them to have the same route: "But I need, that both components will be loaded by '/' route." – eko Jan 30 '17 at 07:11
  • @echonax You cannot have different modules loaded with same route path (even if with different guards). The only choice is to do something like above wherein no matter which path is opened, the user gets redirected correctly. – Santanu Biswas Jan 30 '17 at 07:15
-3

Below code worked for me!!!

Routes = [ { path: '', component: localStorage.getItem('isVisited')?MainComponent:(()=>{ localStorage.setItem('isVisited','true');return AboutComponent})() } ]

vicky
  • 1