Advanced Routing

Routing essentials

Implement Feature-Level-Routing and Aux-Routing like seen in the live coding, if you like.

CanActivateGuard

In this exercise, you will create an AuthService that users can use to log in. This AuthService remembers the current user name and whether the user is logged in. The possibilities of the service should be offered to the user in the HomeComponent.

In addition, create a CanActivate-Guard named AuthGuard, which only allows a route change if the current user is logged in. For this it relies on the AuthService:

  [HomeComponent] -------> [AuthService]
                                 ^
                                 |
                            [AuthGuard]

You can use the following procedure:

  1. Create a auth folder in the project's shared folder.

  2. Create an AuthService in the new folder auth:

    
    @Injectable({
        providedIn: 'root'
    })
    export class AuthService {
    
        userName: string;
    
        constructor() { }
        
        login() {
            this.userName = 'Max';
        }
    
        logout() {
            this.userName = null;
        }
    
    }
    
  3. Create a auth.guard.ts file in the same folder with a CanActivate guard based on AuthService.

    Show code

    @Injectable({ 
        providedIn: 'root' 
    })
    export class AuthGuard implements CanActivate {
    
        constructor(private router: Router, private authService: AuthService) { }
    
        canActivate() {
            if (this.authService.userName) {
                return true;
            }
            return this.router.navigate(['/home', { needsLogin: true }])
        }
    
    }
    

  4. Make sure the AppModule located in app.module.ts imports SharedModule.

    Show code

    
    @NgModule({
      imports: [
         [...]
        SharedModule.forRoot()
      ],
      [...]
    })
    export class AppModule { }
    

  5. Open the file home.component.ts and have the AuthService injected there. Wrap its functionality with methods or setters.

    Show code

    [...]
    export class HomeComponent implements OnInit {
    
      constructor([...], private authService: AuthService) { }
    
      ngOnInit() {
      }
    
      get userName() {
        return this.authService.userName;
      }
    
      login() {
        this.authService.login();
      }
    
      logout() {
        this.authService.logout();
      }
    
    }
    

  6. Open the file home.component.html. Display the current user name.

    Show code

    
    <h1 *ngIf="userName">Welcome, {{userName}}!</h1>
    <h1 *ngIf="!userName">Welcome!</h1>
    

  7. Run the application and check if the implementation works.

  8. Register the AuthGuard in the route configuration in flight-booking.routes.ts to protect one of the established routes.

    Show code

    
    export const FLIGHT_BOOKING_ROUTES: Routes = [
        {
            path: 'flight-search',
            component: FlightSearchComponent,
            canActivate: [AuthGuard],
            [...]
        }
        [...]
    ];
    

  9. Test your solution.

CanDeactivateGuard

In this exercise, you will develop a CanDeactivate-Guard to warn the user before leaving a route.

  1. Create a deactivation folder in the shared folder.

  2. Create a file can-deactivate.guard.ts in the new folder deactivation:

    
    export interface CanDeactivateComponent {
      canDeactivate(): Observable<boolean>;
    }
    
    @Injectable({ providedIn: 'root' })
    export class CanDeactivateGuard implements CanDeactivate<CanDeactivateComponent> {
    
      canDeactivate(
        component: CanDeactivateComponent,
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState?: RouterStateSnapshot): Observable<boolean> {
    
        return component.canDeactivate();
    
      }
    
    }
    

The interface CanDeactivateComponent is used here as an abstraction for the components to be used with the Guard.

  1. Open the file flight-edit.component.ts and implement there the interface CanDeactivateComponent so that on exit one flag is set and on the other hand an Observable<boolean> is returned. The flag causes a warning message to be displayed. Once the user has announced that we really want to leave the route, we want to send true or false to the router via the observable. After that it has to be closed. In addition, the flag should be reset afterwards.

    Show code

    
    export class FlightEditComponent implements OnInit, CanDeactivateComponent {
      
      [...]
    
      sender: Observer<boolean>;
    
      decide(decision: boolean): void {
        this.showWarning = false;
        this.sender.next(decision);
        this.sender.complete();
      }
    
      canDeactivate(): Observable<boolean> {
        return new Observable((sender: Observer<boolean>) => {
          this.sender = sender;
          this.showWarning = true;
        });
    
      }
    
      [...]
    }
    

  2. The file flight-edit.component.html already contains a warning box. It should present two possible strings (yes and no) to the user.

  3. Also register the Guard in the file flight-booking.routes.ts:

    Show code

    
    [...]
    {
        path: 'flight-edit/:id',
        component: FlightEditComponent,
        canDeactivate: [CanDeactivateGuard]
    },
    [...]
    

  4. Test your solution.

Lazy Loading

Implementing Lazy Loading for a feature module

Implement lazy loading for the FlightBookingModule in your app.routes.ts. Keep in mind that lazy loading only works if the module in question isn't referenced directly but only with a string in the router configuration.

  1. Open the file app.module.ts and remove the import for the FlightBookingModule.

    Show Code

    
    @NgModule({
        imports: [
            [...]
            // FlightBookingModule, 
            // ^^ Removed b/c this would prevent lazy loading
            [...]
        ],
        [...]        
    })
    export class AppModule { }
    

  2. Since Angular 8, we are using EcmaScript inline imports for lazy loading. To make them work, you have to adjust your tsconfig.app.json (in flight-app):

    • Here, make sure, module is set to esnext.
  3. Open the file app.routes.ts and introduce a route with the path flight-booking. It should point to the FlightBookingModule using loadChildren:

    Show Code

    [...]
    {
        path: 'flight-booking',
        loadChildren: () => import('./flight-booking/flight-booking.module').then(m => m.FlightBookingModule) 
    },
    {
        // This route needs to be the last one!
        path: '**',
        [...]
    }
    [...]
    

  4. Open the file flight-booking.routes.ts and change the path for the first route to an empty string (path: '') to make this route the default route that is activated after lazy loading the module.

    Show Code

    
    const FLIGHT_BOOKING_ROUTES: Routes = [
    {
        path: '',
        component: FlightBookingComponent,
        [...],
        children: [
            [...]
        ]
    }
    ];
    [...]
    

  5. Find out that webpack splits off an own chunk for the FlightBookingModule after implementing lazy loading. If this works, you will see another chunk at the console (e. g. flight-booking-flight-booking-module.js depending on the used version of the CLI)

  6. Try it out in the browser and use the network tab within the dev tools (F12) to make sure that it is only loaded on demand. If it doesn't work, have a look to the console tab within the dev tools.

Implementing Preloading

In this exercise you will implement Preloading using Angular's PreloadAllModules strategy.

  1. Open the file app.module.ts and register the PreloadAllModules strategy when calling RouterModule.forRoot.

    Show Code

    
    RouterModule.forRoot(APP_ROUTES, {
        preloadingStrategy: PreloadAllModules
    });
    

  2. Make sure it works using the network tab within Chrome's dev tools. If it works, the lazy bundles are loaded after the app has been initializes. If this is the case, the chunks show up quite late in the water fall diagram.