lazy-loaded child route loads twice in Angular

4.6k views Asked by At

I am confronted with the following issue: the personal section which is protected via an AuthGuard Service is loaded twice when I navigate to it or by browser refresh. Second time it strips away the query parameters of the URL if I provide any. Here is my app router configuration:

const routes: Routes = [
  {
    path: 'search',
    redirectTo: '',
    pathMatch: 'full'
  },
  {
    path: '',
    component: BookmarksComponent
  },
  {
    path: 'tagged/:tag',
    component: TagComponent
  },
  {
    path: 'about',
    component: AboutComponent
  },
  {
    path: 'personal',
    loadChildren: 'app/personal/personal-bookmarks.module#PersonalBookmarksModule'
  }
];

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

and the child router configuration

@NgModule({
  imports: [RouterModule.forChild([
    {
      path: '',
      component: PersonalBookmarksComponent,
      canActivate: [AuthGuard],
      children: [
        {
          path: '',
          component: PersonalBookmarksListComponent
        },
        {
          path: 'new',
          component: NewPersonalBookmarkFormComponent
        },
        {
          path: 'bookmarks/:id',
          component: PersonalBookmarkDetailComponent
        }
      ]
    }

  ])],
  exports: [RouterModule]
})
export class PersonalBookmarksRoutingModule {}

The AuthGuard Service (whereby if it only returns true is the same behaviour):

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private keycloakService: KeycloakService) {}

  canActivate() {
    console.log('AuthGuard#canActivate called');

    if (this.keycloakService.isLoggedIn()) {
      return true;
    } else {
      this.keycloakService.login();
    }
  }
}

And the Navbar template:

<nav class="navbar navbar-light bg-faded" id="navbar">
  <a class="navbar-brand" [routerLink]="['']" routerLinkActive="active">
    <img src="assets/logo.png" width="35" height="35" class="d-inline-block align-top" alt="">
    Public Bookmarks
  </a>
  <ul class="nav navbar-nav">
    <li class="nav-item">
      <a class="nav-link" [routerLink]="['personal']" routerLinkActive="active">Personal list</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" [routerLink]="['about']" routerLinkActive="active">About</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="http://www.codingpedia.org/tags/#codingmarks" target="_blank">Blog</a>
    </li>
    <li *ngIf="isLoggedIn else notLoggedIn" class="nav-item">
      <a class="nav-link" (click)="logout()">Logout <i class="fa fa-lock"></i></a>
    </li>
    <ng-template #notLoggedIn>
      <li *ngIf="!keycloakService.isLoggedIn()" class="nav-item">
        <a class="nav-link" (click)="login()">Login <i class="fa fa-unlock"></i></a>
      </li>
    </ng-template>
  </ul>
</nav>

The project is available on Github and the faulty behaviour can be tested at https://www.codingmarks.org/personal by logging in with username/pwd - [email protected]/Test_user1$

UPDATE Even if I remove PersonalBookmarksComponent and move AuthGuard to the PersonalBookmarks module the wrong behaviour still persist... The routes for PersonalBookmarksModule look something like the following:

const personalBookmarksRoutes: Routes = [
    {
      path: 'search',
      redirectTo: '',
      pathMatch: 'full'
    },
    {
      path: '',
      component: PersonalBookmarksListComponent,
      canActivate: [AuthGuard],
    },
    {
      path: 'new',
      component: NewPersonalBookmarkFormComponent
    },
    {
      path: 'bookmarks/:id',
      component: PersonalBookmarkDetailComponent
    }

];

UPDATE2: The stripping away of the query parameters was due to the redirection forced when logging in with Keycloak.

False:

public login(): Promise<any> {
  let options: any;
  options = {redirectUri: environment.HOST + 'personal'};
  return new Promise<any>((resolve, reject) => {
    KeycloakService.auth.login(options)
      .success(resolve)
      .error(reject);
  });
}

Correct:

public login(): Promise<any> {
  return new Promise<any>((resolve, reject) => {
    KeycloakService.auth.login()
      .success(resolve)
      .error(reject);
  });
}
2

There are 2 answers

2
Rahul Singh On

The reason the routes are being loaded twice is because you have specified the router path to load two components when you go to navigate to this ' ' route .

change the router configuration to

@NgModule({
  imports: [RouterModule.forChild([
    {
      path: '',
      //component: PersonalBookmarksComponent, //either remove this component line completely [best way is to remove this component deceleration]
      canActivate: [AuthGuard],
      children: [
        {
          path: '', // or specify a different route to this better use a redirect option on path 
          component: PersonalBookmarksListComponent
        },
        {
          path: 'new',
          component: NewPersonalBookmarkFormComponent
        },
        {
          path: 'bookmarks/:id',
          component: PersonalBookmarkDetailComponent
        }
      ]
    }

  ])],
  exports: [RouterModule]
})
1
Chirag Khanna On

U can perform the following two steps:

  1. Move the authGuard to the personal module routing, it will also improve your performance as the module will not be loaded in case the user not authenticated.
  2. Remove the "PersonalBookmarksComponent" component containing an empty router outlet, instead provide the router for rest of our components directly.

I believe this approach will solve your problem.

The guard is being called twice once for your component and once for your children component.