Functional Guards & Resolvers (v15)

📚 What are Functional Guards?

Functional guards replace class-based guards with simpler functions:

  • CanActivateFn: Controls if route can be activated
  • CanDeactivateFn: Controls if user can leave route
  • CanMatchFn: Controls if route can be matched (replaces CanLoad)
  • ResolveFn: Pre-fetches data before route activation
  • inject() works: Use inject() inside functional guards

🎯 Interview Questions

  • Q1: What is the advantage of functional guards over class-based guards?
  • A: Less boilerplate, no need for classes, better tree-shaking, can use inject() for dependencies.
  • Q2: How do you inject services in functional guards?
  • A: Use inject() function inside the guard: const auth = inject(AuthService);
  • Q3: What replaced CanLoad in Angular 15?
  • A: CanMatchFn - controls whether route can be matched during navigation.
  • Q4: How do you redirect in a functional guard?
  • A: Return router.createUrlTree(['/path']) or router.parseUrl('/path').

🔧 Guard Types Demo

✅ CanActivateFn

Protects routes from unauthorized access

Simulated: User is authenticated ✓

⚠️ CanDeactivateFn

Warns before leaving with unsaved changes

🔄 ResolveFn

Pre-fetches data before route loads

Resolved user: John Doe (john@example.com)

🔄 Class vs Functional Guards

❌ Class-based (Old)

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(
    private auth: AuthService,
    private router: Router
  ) {}
  
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) {
    if (this.auth.isLoggedIn()) {
      return true;
    }
    return this.router.parseUrl('/login');
  }
}

✅ Functional (New)

export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);
  
  if (auth.isLoggedIn()) {
    return true;
  }
  return router.parseUrl('/login');
};

💻 Functional Guards Code


import { inject } from '@angular/core';
import { 
  CanActivateFn, 
  CanDeactivateFn, 
  CanMatchFn, 
  ResolveFn, 
  Router 
} from '@angular/router';

// ===== CanActivateFn - Route Protection =====
export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);
  
  if (auth.isAuthenticated()) {
    return true;
  }
  // Redirect to login with return URL
  return router.createUrlTree(['/login'], {
    queryParams: { returnUrl: state.url }
  });
};

// ===== CanDeactivateFn - Unsaved Changes =====
export const unsavedChangesGuard: CanDeactivateFn<{ hasChanges: () => boolean }> = 
  (component, currentRoute, currentState, nextState) => {
    if (component.hasChanges()) {
      return confirm('Discard unsaved changes?');
    }
    return true;
  };

// ===== CanMatchFn - Feature Flags =====
export const featureGuard: CanMatchFn = (route, segments) => {
  const features = inject(FeatureFlagService);
  return features.isEnabled(route.data?.['feature']);
};

// ===== ResolveFn - Data Pre-fetching =====
export const userResolver: ResolveFn<User> = (route) => {
  const userService = inject(UserService);
  return userService.getUser(route.params['id']);
};

// ===== Using in Routes =====
export const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [authGuard],
    resolve: { user: userResolver }
  },
  {
    path: 'editor',
    component: EditorComponent,
    canDeactivate: [unsavedChangesGuard]
  },
  {
    path: 'beta-feature',
    loadComponent: () => import('./beta.component'),
    canMatch: [featureGuard],
    data: { feature: 'beta' }
  }
];