'Angular - How can I use a direct URL to a lazy loaded angular route?

I have been trying to get lazy loaded routes to work in my app, and so far it works fine when navigating to routes inside the app. I would now like to allow users to directly link to portions of the application once they are already logged in. The way it stands now, they are always directed to the default route instead of to their deep linked route.

Example: If the user requests http://localhost:4200/ui/customers, they are taken to http://localhost:4200/ui/quotes instead (the default route).

I am using a UI component to handle the layout of the application instead of applying those styles into the root of the app itself (inside app.component). Each of the subsequent Modules also follows a similar structure with the root managing the layout and deeper links providing the implementations.

Is there something I need to do differently to allow my routes to be activated via a direct link?

I tried using canLoad, canActivate, and canActivateChild to make sure that wasn't blocking the routes, but even removing it altogether, users still cannot reach a deep link.

Note: I am using Angular 13 with Nrwl in a mono-repo configuration.

Code:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard, authRoutes } from '@my-app/auth';

const routes: Routes = [
  {
    path: 'ui',
    loadChildren: () => import('@my-app/ui').then((m) => m.UiModule),
    canLoad: [AuthGuard],
    //canActivate: [AuthGuard],
    //canActivateChild: [AuthGuard],
  },
  { path: 'auth', children: authRoutes },
  { path: '**', redirectTo: 'ui', pathMatch: 'full' },
];

@NgModule({
  exports: [RouterModule],
  imports: [
    RouterModule.forRoot(routes, {
      onSameUrlNavigation: 'reload',
      initialNavigation: 'enabled',
    }),
  ],
})
export class AppRoutingModule {}

app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AuthModule } from '@my-app/auth';
import { UiModule } from '@my-app/ui';
import { UiAlertModule } from '@my-app/ui-alert';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    BrowserAnimationsModule,
    StoreModule.forRoot(
      {},
      {
        metaReducers: !environment.production ? [] : [],
        runtimeChecks: {
          strictActionImmutability: true,
          strictStateImmutability: true,
        },
      }
    ),
    EffectsModule.forRoot([]),
    !environment.production ? StoreDevtoolsModule.instrument() : [],
    AppRoutingModule,
    AuthModule,
    UiModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

auth.guard.ts

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  CanActivateChild,
  CanLoad,
  Route,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthFacade } from '../../+state/auth.facade';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
  constructor(private router: Router, private authFacade: AuthFacade) {}
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    const url: string = state.url;
    return this.handleGuard(url);
  }

  canActivateChild(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    const url: string = state.url;
    return this.handleGuard(url);
  }

  canLoad(route: Route): Observable<boolean> {
    const url = `/${route.path}`;

    return this.handleGuard(url);
  }

  private handleGuard(url: string) {
    this.router.events.subscribe((event) => {
      // eslint-disable-next-line no-restricted-syntax
      console.debug('GUARD', event, url);
    });
    return this.authFacade.userToken$.pipe(
      map((user) => {
        if (user) {
          return true;
        } else {
          this.router.navigate([`/auth/login`]);
          return false;
        }
      })
    );
  }
}

ui-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LayoutComponent } from './layout/layout.component';

const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    children: [
      {
        path: 'customers',
        loadChildren: () =>
          import('@my-app/ui-customers').then((m) => m.UiCustomersModule),
      },
      {
        path: 'quotes',
        loadChildren: () =>
          import('@my-app/ui-quotes').then((m) => m.UiQuotesModule),
      },
      {
        path: 'users',
        loadChildren: () =>
          import('@my-app/ui-users').then((m) => m.UiUsersModule),
      },
      { path: '', redirectTo: 'quotes', pathMatch: 'full' },
    ],
  },
];

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

ui.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AuthModule } from '@my-app/auth';
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
import { LayoutComponent } from './layout/layout.component';
import { UiRoutingModule } from './ui-routing.module';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    TypeaheadModule.forRoot(),
    UiRoutingModule,
    AuthModule,
  ],
  declarations: [LayoutComponent],
  exports: [LayoutComponent],
  providers: [],
})
export class UiModule {}

Thank you for your help and consideration!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source