16

I would like to have two top level paths for logging in and registering.

I would prefer not having to do auth/log-in and auth/register.

However, the auth components are in a separate module, which is good because it shouldn't be loaded unless specifically requested.

export const routes: Route[] = [
  { path: 'log-in',   loadChildren: './auth-module/auth.module#AuthModule'},
  { path: 'register', loadChildren: './auth-module/auth.module#AuthModule'}
];

How can I specify when I am defining my routes that I want the log-in path to go to the log-in path inside the lazy loaded AuthModule, and the register path to go to the register path inside the lazy loaded module?

CodyBugstein
  • 21,984
  • 61
  • 207
  • 363
  • I have the same issue, I can't understand how Angular don't allow this kind of configuration https://stackoverflow.com/questions/49435458/angular-reuse-same-lazy-load-module-for-multiple-root-paths – Javier Marín Apr 09 '18 at 14:45
  • 1
    Lazy-loading in Angular doesn't happen at the component but at the module level. If you try to lazy-load the way you're doing, it will load all components for both routes, anyway. So, it doesn't matter. If you'd like to lazy load a specific component, an alternative would be to create a module for it. Then, you'd have two separate modules (LoginModule & RegisterModule). – Will Apr 22 '18 at 07:10
  • 1
    @Will thanks for the comment. For the purposes of good code organization I want to keep my modules grouped logically and not have so many single component modules. Your suggestion is not terrible for this situation but is not practical for my other modules which have more declared components – CodyBugstein Apr 22 '18 at 08:07
  • Can you please add your `auth-module` routes to your question? – Tjaart van der Walt Apr 23 '18 at 13:22
  • @TjaartvanderWalt my `auth-module` routes are obviously the same. I want there to be a `log-in` and `register` component in my auth module – CodyBugstein Apr 23 '18 at 17:17

4 Answers4

9

Update

It is possible, even for lazyloaded modules to define an empty route.

const APP_ROUTES: Routes = [
    { path: '', () => import('./app/auth/auth.module').then(m => m.AuthModule)},
];

And in your lazy loaded module define the path for both components

const MODULE_ROUTES: Routes = [
    { path: 'login', component: LoginComponent},
    { path: 'register', component: RegisterComponent }
];

The module is only loaded when a component path is matched

Original Awnser

Up to now it is not possible to configure routes as you wanted to do in your post. You can see that if you enable logging for your router. The path for your module (login, register) is consumed and in your module routing you'll just have '' left, which can not be matched to two different components.

You can achive this none the less, even if it's not that pretty and I don't know how/if it works in older browsers.

In your app.module.ts

const APP_ROUTES: Routes = [
    { path: 'login', redirectTo: 'auth/login', pathMatch: 'full' },
    { path: 'register', redirectTo: 'auth/register', pathMatch: 'full' },
    { path: 'auth', loadChildren: 'app/auth/auth.module#AuthModule', pathMatch: 'prefix'},
];

@NgModule( {
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        RouterModule,
        RouterModule.forRoot(APP_ROUTES)
    ],
    providers: [
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

and the auth.module.ts looks like this

const ROUTES: Routes = [
    { path: 'login', component: LoginComponent, pathMatch: 'full' },
    { path: 'register', component: RegisterComponent, pathMatch: 'full' }
];

@NgModule( {
    imports: [
        CommonModule,
        RouterModule.forChild( ROUTES )
    ],
    declarations: [
        LoginComponent,
        RegisterComponent
    ]   
} )
export class AuthModule { }

You can then access the routes by <a routerLink="login">Login</a>, works with router.navigate as well. Unfortunally this will leave you with auth/login or auth/register in your browsers url even if the client calls just /login.

You can fix that by creating an entry in your window.history - as I said not to pretty and that has to be done in your components constructor. window.history.pushState('register', 'Register', 'register'); or window.history.pushState('login', 'Login', 'login'); in the components will leave your browsers url with /login and /register.

The best way would of cause be to extend the angular router with this functionallity and use that as custom router, but you'd have to get really into it for that and with the next update you might get screwed.

Maybe this helps you

Best regards

Fussel
  • 1,740
  • 1
  • 8
  • 27
7
export const routes: Route[] = [
  { path: 'log-in',   loadChildren: './auth-module/auth.module#AuthModule'},
  { path: 'register', loadChildren: './auth-module/auth.module#AuthModule'}
];

I will try to say it's not possible. Lazy loading modules loaded by redirects. This means:

  • Start to navigate log-in.
  • Lazy loading module starts to load
  • After successfully loaded, url will be somePath/log-in
  • So, what's the next? Here redirection process is started. So each lazy loading module's routing.ts should contain entry point path like:

    {
       path: '',
       redirectTo: 'somePath/ChildPath' // or component: SomeComponent
    }
    

Doesn't matter for which route it navigated, if it's a lazy module, then it will try to find entry path. In your case two routes loading the same module, but also they reference to the same entry route path (path: '').

If you really want to divide them, they should allocated in different modules.

export const routes: Route[] = [
  { path: 'log-in',   loadChildren: './auth-module/someModule#someModule'},
  { path: 'register', loadChildren: './auth-module/auth.module#AuthModule'}
];

some-module.routing.ts:

{
    path: '',
    component: LoginComponent,

},

UPDATE 29.04.18. Simple solution with one more component

Solution is to create additional component which acts as renderer of other components depending on the current url.

app-routing.module:

const appRoutes: Routes = [
 ...

  {
    path: 'admin',
    loadChildren: 'app/admin/admin.module#AdminModule',

  },
  {
    path: 'login',
    loadChildren: 'app/admin/admin.module#AdminModule',

  },
  {
    path: 'crisis-center',
    loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',
    // data: { preload: true }
  },
  { path: '', redirectTo: '/superheroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

In admin.module create renderer component

central.component.ts:

...

@Component({
  selector: 'app-central',
  templateUrl: './central.component.html',
  styleUrls: ['./central.component.css']
})
export class CentralComponent implements OnInit {

  currentComponent: LoginComponent | AdminComponent;

  constructor(public router: Router, public activatedRoute: ActivatedRoute) { }

  ngOnInit() {
    if (this.router.url === '/login') {
      this.currentComponent = LoginComponent;
    } else {
      this.currentComponent = AdminComponent;
    }
  }
}

central.component.template:

<ng-container *ngComponentOutlet="currentComponent"></ng-container>

So, as you can see, central component will dynamically render component depending on url path.

For rendering used declarative approach method with NgComponentOutlet

More detail about imperative and declarative approach: https://stackoverflow.com/a/49840700/5677886

StackBlitz Demo

Yerkon
  • 4,548
  • 1
  • 18
  • 30
  • Ya it seems you can only do one direct route per lazy loaded module. – CodyBugstein Apr 29 '18 at 08:32
  • but when does CentralComponent get loaded? I don't see any reference to it in the routes or module, etc – CodyBugstein Apr 30 '18 at 20:50
  • Ok now I see it after exploring your stackblitz. This is brilliant! Thank you so much! (the problem is I didn't see this in time and SO automatically awarded the bounty to the other (hacky ) answer :( ) – CodyBugstein Apr 30 '18 at 20:53
  • @CodyBugstein, Glat to hear. It's OK – Yerkon May 01 '18 at 06:34
  • @Yerkon If **both** the `LoginComponent` and `AdminComponent` implement `OnInit` then you get a typescript error saying `Property 'ngOnInit' is missing in type 'typeof LoginComponent' but required in type 'LoginComponent'`. Why is that? What does it really mean? – atiyar Sep 23 '19 at 16:31
1

The app-routing module can prefix (or not) a feature module's routes. If you don't want a route prefix like /auth, either using lazy-loading or no) you can do something like this:

app-routing.module.ts:

  {
    path:             '',
    canActivate:      [AuthGuard],
    canActivateChild: [AuthGuard],
    children:         [
      {
        path:      '',
        component: HomeComponent,
      },
      {
        path:         '',
        loadChildren: 'app/auth/auth.module#AuthModule',
      },
      {
        path:      'settings',
        component: SettingsComponent,
      },
    ],
  },

The top-route of the lazy-loaded module does not even have to be static, you can have a configuration like:

auth-routing.module.ts:

const routes: Routes = [
  {
    path:      ':pageName',
    component: AuthComponent,
    resolve:   {
      pageInfo: AuthResolver,
    },
    children:  [
      {
        path:      'login',
        component: LoginComponent,
      },
      {
        path:      'register',
        component: RegisterComponent,
      },
    ],
  },
];
Mike Drakoulelis
  • 762
  • 11
  • 25
-2

You need to define what happens when you arrive at the lazy loaded AuthModule. Do that by creating/importing a routing file for your AuthModule using ModuleWithProviders. Child routes and router outlets can be added if needed.

import { ModuleWithProviders } from '@angular/core';

export const authRoutes: Route[] = [

  { 
    path: '', 

 // path: '', redirects to children with no prefix in route
 // path: 'auth', prefix the route with 'auth' to create 'auth/log-in' 'auth/register'

    children: [                     
        { 
           path: 'log-in',
           component: LoginComponent,
          // outlet:'login' 
        },
        { 
            path: 'register', 
            component: RegisterComponent,
        //  outlet:'register' 
        },

        // redirects can be added to force the application to load your desired route 
       //{ path: '', redirectTo:'log-in' ,pathMatch:'full'},
    ]},
];
export const AuthRouting: ModuleWithProviders = RouterModule.forChild(authRoutes);

Your AuthModule then imports this routing file .

//other imports
import {AuthRouting} from 'some/path/location';

@NgModule({
  imports: [
    CommonModule,
    AuthRouting,
    RouterModule
  ],
James Fooks
  • 69
  • 1
  • 7
  • Ok but a visitor to my app would still have to navigate to `auth/log-in`. I want the path to exclude the `auth` – CodyBugstein Mar 30 '18 at 04:58
  • then do not use the //commented lines - // path 'auth', as i stated, leave the first path empty. Anything prefixed with // above is just a comment line and would not run. – James Fooks Mar 30 '18 at 05:11
  • And what does the main module look like? How do my routes even get to the AuthRoutingModule? – CodyBugstein Mar 30 '18 at 06:12
  • Your answer is not a solution to the problem I am asking about. In fact it is a further illustration of the problem – CodyBugstein Mar 30 '18 at 06:14
  • your main app routing is the same, as you have it already lazy loading the AuthModule as /log-in or /register are requested. The authrouting then takes over when your AuthModule loads and assuming you have the initial path empty path:' ', it will re route to child routes - get augury for chorme and you can see the paths you define for all routes or add enableTracing: true to the main app routing - RouterModule.forRoot( appRoutes, { enableTracing: true } ) – James Fooks Mar 30 '18 at 14:09