Search code examples
angularangular-routingangular7

Header disappears on Angular 7 router


I recently upgraded from angular 5 to 7. After watching this presentation by Deborah Kurata from the most recent ng-conf, I decided to update my rather terrible routing code.

I decided to implement her idea of a shell component on top of the app component to handle routing, then lazy-load each feature module as and when needed. Here's what I have so far:

app.component.html

<router-outlet></router-outlet>

app-routing.module.ts

import { Injectable, NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AccessGuard }           from './shared/guards/access.guard';
import { LoginComponent }        from './login/login.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

export const appRoutes: Routes = [
  { 
    path: 'login', 
    component: LoginComponent, 
    data: { requiresLogin: false },  
    canActivate: [ AccessGuard ] 
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [ RouterModule.forRoot(appRoutes) ],
  exports: [ RouterModule ],
  providers: [ AccessGuard ]
})
export class AppRoutingModule {}

So here you can see that I'm only concerned with the login and page-not-found views in the app root. Below is the shell.module that should be the primary router for everything else:

shell.component.html

<app-header></app-header>
<router-outlet name="primary"></router-outlet>

shell-routing.module.ts

import { Injectable, NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Observable }           from 'rxjs';

import { AccessGuard }        from '../shared/guards/access.guard';
import { DashboardComponent } from '../dashboard/dashboard.component';
import { ShellComponent }     from './shell.component';

export const shellRoutes: Routes = [
  { 
    path: '', 
    component: ShellComponent,
    data: { requiresLogin: true },
    canActivate: [ AccessGuard ],
    children: [
      { 
        path: 'dashboard', 
        component: DashboardComponent,
        data: { requiresLogin: true },
        canActivate: [ AccessGuard ] 
      },
      {
        path: 'posts',
        loadChildren: 'app/posts/posts.module#PostsModule'
      },
      {
        path: 'user-profile',
        loadChildren: 'app/user-profile/user-profile.module#UserProfileModule'
      }
    ]
  },
  { 
    path: '',
    redirectTo: '/dashboard', 
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [ RouterModule.forChild(shellRoutes) ],
  exports: [ RouterModule ],
  providers: [ AccessGuard ]
})
export class ShellRoutingModule {}

Again, as you can see, I'm lazy loading the Posts and UserProfile modules. And finally the aforementioned module routes:

posts-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AccessGuard }    from '../shared/guards/access.guard';
import { PostsComponent } from './posts.component';
import { PostComponent }  from './post/post.component';

const postsRoutes: Routes = [
  {
    path: '',
    redirectTo: 'add',
    pathMatch: 'full'
  },
  {
    path: 'add',
    component: PostComponent,
    data: { requiresLogin: true },
    canActivate: [ AccessGuard ],
  },
  {
    path: 'comment/:id',
    component: PostComponent,
    data: { requiresLogin: true },
    canActivate: [ AccessGuard ],
  },
  {
    path: 'edit/:id',
    component: PostComponent,
    data: { requiresLogin: true },
    canActivate: [ AccessGuard ],
  } 
];

@NgModule({
  imports: [ RouterModule.forChild(postsRoutes) ],
  exports: [ RouterModule ],
  providers: [ AccessGuard ]
})
export class PostsRoutingModule { }

user-profile-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AccessGuard }  from '../shared/guards/access.guard';

import { UserProfileComponent }   from './user-profile.component';
import { FollowersComponent }     from './followers/followers.component';
import { FollowingComponent }     from './following/following.component';
import { MentorsComponent }       from './mentors/mentors.component';
import { CoachesComponent }       from './coaches/coaches.component';
import { NotificationsComponent } from './notifications/notifications.component';
import { AdminMentorComponent }   from './admin-mentor/admin-mentor.component';

const userProfileRoutes: Routes = [ 
  {
    path: 'user-profile',
    data: { requiresLogin: true },
    children: [
      {
        path: ':id',
        data: { requiresLogin: true },
        canActivate: [ AccessGuard ],
        children: [
          {
            path: '',
            component: UserProfileComponent,
          },
          {
            path: 'followers',
            component: FollowersComponent
          },
          {
            path: 'following',
            component: FollowingComponent
          },
          {
            path: 'mentors',
            component: MentorsComponent
          },
          {
            path: 'coaches',
            component: CoachesComponent
          },
          {
            path: 'notifications',
            component: NotificationsComponent
          }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [ RouterModule.forChild(userProfileRoutes) ],
  exports: [ RouterModule ],
  providers: [ AccessGuard ]
})
export class UserProfileRoutingModule { }

Question

Why does, based on the above, the header appear on the posts routes but doesn't appear on the user profile routes?


EDIT

I can't emulate the behaviour on StackBlitz unfortunately. Everything appears. I'll comment out my header.component code and replace it with something else to see if it appears.


EDIT 2

As mentioned in my previous edit I can' emulate the behaviour correctly. The only other way I can think of to demo my problem is this: when I navigate to the posts route like this:

<input placeholder="How are you today?" routerLink="/posts/add">

I can see in the DOM tree that the correct router-outlet is targeted (the one in the shell.component) and the post component (app-post) is slotted in beside it:

enter image description here

However, the components for the user-profile doesn't do this. They instead go in beside the app.component instead of the one in the shell.component. I hardcoded the route to the user-profile like this:

<p routerLink="/user-profile/6">Testing testing</p>

to test but got the same disappearing header result.

enter image description here

This is really odd behaviour to me. Both component routes are imported into the shell-routing.module and so should slot their components in beside it.

So what gives?

Also - am I correct in saying that when you attempt to navigate via "/some-path/here" you are using an absolute path instead of a relative one so it should work? Doot doot


Solution

  • The reason this occurs is because when you lazy load a module in Angular and define a route as a child its parent must contain a router-outlet in order to add the child, otherwise it won't work.

    Another key point is that when changes are made, e.g. the addition of a lazy loaded module or modification to its routes, you need to restart your app and serve it with the --aot flag:

    ng serve --aot
    

    to see your changes take effect.