Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular router links do not work until app is reloaded with browser's refresh button

In my Angular(v7) application, clicking on a router link does not load the relevant component although the URL is changed in the browsers address bar.

Once the page is reloaded clicking on the refresh button of the browser then the router links start to work as expected. I have confirmed the behaviour in both Google Chrome and IE.

Could you please show me what I'm doing wrong and where? I have shared the relevant parts of my code below.

App module & router setup

import {
  RouterModule,
  Routes
} from "@angular/router";
...

const appRoutes: Routes = [{
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'bookings', 
    component: BookingsComponent, 
    canActivate: [AuthGuard]}, 
  {
    path: 'rates', 
    component: RatesComponent, 
    canActivate: [AuthGuard]},
  {
    path: '',
    redirectTo: '/bookings',
    pathMatch: 'full',
    canActivate: [AuthGuard]
  },
  {
    path: '**',
    component: PageNotFoundComponent
  },
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes, {
      enableTracing: false
    }),
    BrowserModule,
    ...
  ]
})
export class AppModule {}

Router Links in HTML template

<a class="side-nav-link" routerLink="/rates" routerLinkActive="active">Rates</a>
<a class="side-nav-link" routerLink="/bookings" routerLinkActive="active">Bookings</a>

Auth Guard, If it is relevant

@Injectable({
  providedIn: "root"
})
export class AuthGuard implements CanActivate {

  constructor(private router: Router, private authService:AuthService) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    if (this.authService.loggedIn) {
      return true;
    }

    this.router.navigate( ['/login'], { queryParams: { returnUrl: state.url }});
    return false;
  }
}
like image 433
Johna Avatar asked Mar 05 '23 08:03

Johna


1 Answers

I had the same problem. It was caused by having two router-outlets on a single page and switching between the two using an ngIf. This seems to be an Angular anti-pattern.

Cause

Here is an example of what I was doing:

File: app.component.html

<!-- Show toolbar and sidenav if logged in -->
<mat-toolbar *ngIf="isLoggedIn()">My App</mat-toolbar>
<mat-sidenav-container *ngIf="isLoggedIn()">
  <mat-sidenav>
    <mat-nav-list>
      <a mat-list-item routerLink="/page1">Page 1</a>
      <a mat-list-item routerLink="/page2">Page 2</a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

<!-- Hide toolbar and sidenav if NOT logged in -->
<router-outlet *ngIf="isLoggedIn() === false"></router-outlet>

File: app-routing.module.ts

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'page-1', component: Page1Component, canActivate: [AuthGuard] },
  { path: 'page-2', component: Page2Component, canActivate: [AuthGuard] },
  { path: '**', redirectTo: 'page-1' }
];

I did this because I wanted two layouts: One for my main app content (with a toolbar and side-nav) and another for my login page (no toolbar and side-nav).

Solution

The correct way to do this is to create a two components, one for each of the layouts I wanted, and then use the routers children property. Link to docs. For example:

File: app.component.html

<router-outlet></router-outlet>

File: layouts/main-layout.component.html

<mat-toolbar>My App</mat-toolbar>
<mat-sidenav-container>
  <mat-sidenav>
    <mat-nav-list>
      <a mat-list-item routerLink="/page1">Page 1</a>
      <a mat-list-item routerLink="/page2">Page 2</a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

File: layouts/login-layout.component.html

<router-outlet></router-outlet>

File: app-routing.module.ts

const routes: Routes = [
  {
    path: 'login', component: LoginLayoutComponent, children: [
      { path: '', component: LoginComponent },
    ]
  },
  {
    path: '', component: MainLayoutComponent, children: [
      { path: 'page-1', component: Page1Component, canActivate: [AuthGuard] },
      { path: 'page-2', component: Page2Component, canActivate: [AuthGuard] },
      { path: '**', redirectTo: 'page-1' }
    ]
  },
];

Hope this helps :)

like image 89
David MacCallum Avatar answered Apr 09 '23 14:04

David MacCallum