typescript 85 lines · 3 tabs

Guarding a Lazy-Loaded Angular Admin Route with a Functional CanActivate Role Check

Shared by codesnips Jul 2026
3 tabs
import { Injectable, signal, computed } from '@angular/core';

export interface CurrentUser {
  id: string;
  email: string;
  roles: string[];
}

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly user = signal<CurrentUser | null>(null);

  readonly isLoggedIn = computed(() => this.user() !== null);

  setUser(user: CurrentUser | null): void {
    this.user.set(user);
  }

  hasRole(role: string): boolean {
    const current = this.user();
    return current !== null && current.roles.includes(role);
  }
}
3 files · typescript Explain with highlit

Lazy-loaded feature modules keep the initial bundle small, but they also create a natural authorization boundary: the admin chunk should never even download for a user who lacks the right role, and its routes should never render if the role check fails. This snippet wires a functional CanActivate guard onto a loadChildren route so both concerns are handled in one place.

In auth.service.ts, the current user is exposed through a signal, and hasRole derives authorization state synchronously from that signal. Keeping roles in a signal means the guard can read them without subscribing, and any component can react to login changes reactively. The service is providedIn: 'root' so the same instance is shared by the guard, the components, and the app shell.

admin.guard.ts shows the modern functional guard style: authGuard is a plain CanActivateFn, so it runs inside an injection context and uses inject() to pull in AuthService and Router rather than being a class with a constructor. The requireRole factory closes over a role string and returns a fresh CanActivateFn, which makes the same logic reusable for admin, editor, or any future role. When the check fails, the guard returns a UrlTree from router.parseUrl instead of false; returning a UrlTree tells the router to cancel the current navigation and redirect atomically, avoiding a flash of a blank or partially-activated route. The returnUrl query param preserves where the user was heading so login can bounce them back.

app.routes.ts attaches requireRole('admin') to the canActivate array of the lazily loadChildren-ed admin route. Because guards run before the router fetches the lazy chunk, an unauthorized user never triggers the network request for the admin code — a real security and performance win over guarding only inside the component.

A subtle pitfall worth noting: guards protect navigation, not the API. The server must still enforce roles, since a determined user can call endpoints directly. This client-side guard is about UX and defense in depth, not the final authority. The functional style also composes cleanly — multiple CanActivateFns in the array run in order, short-circuiting on the first that denies.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
python
class Product(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    margin = models.DecimalField(max_digits=5, decimal_places=2, blank=True)

Django model signals vs overriding save

django python models
by Priya Sharma 2 tabs
kotlin
package com.example.myapp

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp

Dependency injection with Hilt

kotlin android hilt
by Alex Chen 3 tabs
python
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [

Django URL namespacing and reverse lookups

django python urls
by Priya Sharma 3 tabs
bash
#!/usr/bin/env bash
set -euo pipefail

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"

Secrets management with environment isolation and Vault

secrets-management vault environment-variables
by Kai Nakamura 1 tab

Share this code

Here's the card — post it anywhere.

Guarding a Lazy-Loaded Angular Admin Route with a Functional CanActivate Role Check — share card
Link copied