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

9
  • What is the error? Commented Jan 30, 2017 at 4:28
  • I'm sorry. There is no error. But performed only first router rule Commented Jan 30, 2017 at 5:21
  • If there's no error and first router rule is satisfied then your LoggedInGuard returned true? Commented Jan 30, 2017 at 5:22
  • I was trying return false from LoggedInGuard and true from GuestGuard. But the second rule still not performed Commented Jan 30, 2017 at 5:32
  • I've create a plunker sketch: embed.plnkr.co/VaiibEVGE79QU8toWSg6 Commented Jan 30, 2017 at 6:18

4 Answers 4

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;
})
Sign up to request clarification or add additional context in comments.

4 Comments

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!
You'll get circular dependency injecting service into main.ts.
Anton Stepanenkov, could you please explain this example in Angular 8? Your example is not working, applicationInjector returns undefined...
I would advise to use @German Quinteros response, because here we expose root injector to global scope.
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

5 Comments

great flexible solution!!
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
Facing this error "ERROR in Cannot read property 'loadChildren' of undefined" in generation AOT build
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: github.com/gquinteros93/same-path-for-different-modules/issues/…
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.
-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;
    }
}

2 Comments

OP wants both of them to have the same route: "But I need, that both components will be loaded by '/' route."
@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.
-3

Below code worked for me!!!

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.