Understanding Angular Route Guards: canActivate, canDeactivate, canActivateChild, and canLoad

2 분 소요

This article explores the use of route guards in Angular, focusing on canActivate, canDeactivate, canActivateChild, and canLoad. We’ll demonstrate how to implement these guards to control route access and module loading.

  • canLoad: Prevents the loading of a module, primarily used with lazy-loaded modules. It executes only once when the module is initially loaded.
  • canActivate: Protects routes by preventing access to unauthorized users. It runs every time a route is activated.
  • resolve: Executes a function before a route is activated, providing pre-fetched data to the component.
  • canDeactivate: Executes a function before navigating away from a route, allowing you to prevent the user from leaving the page under certain conditions.

While it’s possible to combine multiple functionalities into a single guard, creating separate guards for each specific purpose promotes clarity and maintainability. For demonstration purposes, the following example combines several guard implementations into one.

Routing Module Configuration

Within your routing module, configure the resolve option to execute a guard and assign its return value to a variable. The syntax is {variableName: GuardName}.

import { MainGuard } from '../../guards/main.guard';
import { Routes } from '@angular/router';
import { MainComponent } from './main.component'; // Assuming MainComponent is in the same directory, adjust as necessary

const routes: Routes = [
  {
    path: '',
    canActivateChild: [MainGuard],
    children: [
      { path: '', redirectTo: 'main', pathMatch: 'full' },
      {
        path: 'main',
        component: MainComponent,
        canActivate: [MainGuard],
        resolve: { itemList: MainGuard },
        canDeactivate: [MainGuard],
      },
      {
        path: 'login',
        loadChildren: () => import('../login/login.module').then(m => m.LoginModule),
        canLoad: [MainGuard],
        data: { preload: true },
      },
    ],
  },
];

Implementing the Guard

Create the guard class implementing the CanLoad, CanActivate, CanActivateChild, Resolve, and CanDeactivate interfaces.

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  CanLoad,
  CanActivate,
  CanActivateChild,
  Resolve,
  CanDeactivate,
  Route,
  UrlTree,
  Router,
} from '@angular/router';
import { Observable } from 'rxjs';
// service
import { ItemService } from '../services/item.service';
import { UserService } from '../services/user.service';
import { Item } from '../models/item.model'; // Adjust path as necessary

@Injectable({
  providedIn: 'root',
})
export class MainGuard
  implements CanLoad, CanActivate, CanActivateChild, Resolve<Item[]>, CanDeactivate<boolean>
{
  constructor(
    private itemService: ItemService,
    private userService: UserService,
    private router: Router,
  ) {}

  canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean {
    // Additional logic before loading the module.
    this.itemService.onInit();
    return true;
  }

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    if (!this.userService.userInfo || this.userService.userInfo.isGuest) {
      this.router.navigate(['/login']);
      return false;
    }
    return true;
  }

  canActivateChild(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    return true;
  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Item[]> | Promise<Item[]> | Item[] {
    return this.itemService.get();
  }

  canDeactivate(): boolean {
    this.itemService.clear();
    return true;
  }
}

Using Resolved Data in the Component

Access the data resolved by the guard in your component using the ActivatedRoute.

import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, NgZone } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
    selector: 'app-main',
    templateUrl: './main.component.html',  // Adjust path
    styleUrls: ['./main.component.scss'],  // Adjust path
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MainComponent implements OnInit {
  itemList: any[];

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private cd: ChangeDetectorRef,
    private zone: NgZone
  ) {}

  ngOnInit() {
    this.itemList = this.route.snapshot.data['itemList'];
  }
}

댓글남기기