or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-features.mddirectives.mdindex.mdnavigation-state.mdroute-config.mdrouting-core.mdurl-handling.md
tile.json

route-config.mddocs/

Route Configuration

Route definitions, configuration options, guards, and resolvers for declarative routing setup. Provides comprehensive control over route matching, component loading, access control, and data resolution.

Capabilities

Route Interface

Core route configuration interface defining single route with all available options.

/**
 * Defines single route configuration
 */
interface Route {
  /** Path to match against URL segments */
  path?: string;
  /** Path-matching strategy: 'prefix' matches partial path, 'full' requires exact match */
  pathMatch?: 'prefix' | 'full';
  /** Custom URL-matching function for complex route patterns */
  matcher?: UrlMatcher;
  /** Component to instantiate when route matches */
  component?: Type<any>;
  /** Lazy-loaded component function */
  loadComponent?: () => Type<unknown> | Observable<Type<unknown>> | Promise<Type<unknown>>;
  /** URL or function for redirect when route matches */
  redirectTo?: string | RedirectFunction;
  /** Named outlet for component placement */
  outlet?: string;
  /** Guards controlling route activation */
  canActivate?: Array<CanActivateFn | DeprecatedGuard>;
  /** Guards controlling route matching */
  canMatch?: Array<CanMatchFn | DeprecatedGuard>;
  /** Guards controlling child route activation */
  canActivateChild?: Array<CanActivateChildFn | DeprecatedGuard>;
  /** Guards controlling route deactivation */
  canDeactivate?: Array<CanDeactivateFn<any> | DeprecatedGuard>;
  /** Guards controlling route loading (deprecated, use canMatch) */
  canLoad?: Array<CanLoadFn | DeprecatedGuard>;
  /** Additional static data passed to component */
  data?: Data;
  /** Data resolvers for pre-loading route data */
  resolve?: ResolveData;
  /** Child route configurations */
  children?: Routes;
  /** Lazy-loaded child routes */
  loadChildren?: LoadChildren;
  /** Policy for when to run guards and resolvers */
  runGuardsAndResolvers?: RunGuardsAndResolvers;
  /** Providers scoped to this route */
  providers?: Array<Provider | EnvironmentProviders>;
  /** Page title resolver */
  title?: string | Type<Resolve<string>> | ResolveFn<string>;
}

/** Array of Route configurations */
type Routes = Route[];

Route Configuration Examples:

import { Routes } from '@angular/router';

export const routes: Routes = [
  // Basic component route
  { path: 'home', component: HomeComponent },
  
  // Route with parameters
  { path: 'users/:id', component: UserComponent },
  
  // Route with multiple parameters
  { path: 'posts/:year/:month/:slug', component: PostComponent },
  
  // Route with optional parameters (matrix parameters)
  { path: 'search', component: SearchComponent },
  
  // Redirect route
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  
  // Route with static data
  {
    path: 'about',
    component: AboutComponent,
    data: { title: 'About Us', section: 'info' }
  },
  
  // Route with guards
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [authGuard, adminGuard]
  },
  
  // Route with resolver
  {
    path: 'profile/:id',
    component: ProfileComponent,
    resolve: { user: userResolver }
  },
  
  // Lazy-loaded component
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
  },
  
  // Child routes
  {
    path: 'products',
    component: ProductsComponent,
    children: [
      { path: '', component: ProductListComponent },
      { path: ':id', component: ProductDetailComponent },
      { path: ':id/edit', component: ProductEditComponent }
    ]
  },
  
  // Lazy-loaded module
  {
    path: 'feature',
    loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  },
  
  // Auxiliary routes (named outlets)
  { path: 'sidebar', component: SidebarComponent, outlet: 'sidebar' },
  
  // Wildcard route (must be last)
  { path: '**', component: PageNotFoundComponent }
];

Route Guards

Functional guards for controlling route access, activation, and deactivation.

/**
 * Guard controlling route activation
 * @param route Snapshot of activated route
 * @param state Snapshot of router state
 * @returns Promise/Observable/boolean indicating whether route can activate
 */
type CanActivateFn = (
  route: ActivatedRouteSnapshot, 
  state: RouterStateSnapshot
) => MaybeAsync<GuardResult>;

/**
 * Guard controlling child route activation
 * @param childRoute Snapshot of child route being activated
 * @param state Snapshot of router state
 * @returns Promise/Observable/boolean indicating whether child can activate
 */
type CanActivateChildFn = (
  childRoute: ActivatedRouteSnapshot, 
  state: RouterStateSnapshot
) => MaybeAsync<GuardResult>;

/**
 * Guard controlling route deactivation
 * @param component Current component instance
 * @param currentRoute Current activated route snapshot
 * @param currentState Current router state
 * @param nextState Next router state
 * @returns Promise/Observable/boolean indicating whether route can deactivate
 */
type CanDeactivateFn<T> = (
  component: T,
  currentRoute: ActivatedRouteSnapshot,
  currentState: RouterStateSnapshot,
  nextState: RouterStateSnapshot
) => MaybeAsync<GuardResult>;

/**
 * Guard controlling route matching
 * @param route Route configuration
 * @param segments URL segments being matched
 * @returns Promise/Observable/boolean indicating whether route can match
 */
type CanMatchFn = (
  route: Route, 
  segments: UrlSegment[]
) => MaybeAsync<GuardResult>;

/**
 * Guard controlling route loading (deprecated, use CanMatchFn)
 * @param route Route configuration
 * @param segments URL segments being loaded
 * @returns Promise/Observable/boolean indicating whether route can load
 */
type CanLoadFn = (
  route: Route, 
  segments: UrlSegment[]
) => MaybeAsync<GuardResult>;

Guard Examples:

import { inject } from '@angular/core';
import { CanActivateFn, CanDeactivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

// Authentication guard
export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.isLoggedIn()) {
    return true;
  }
  
  return router.createUrlTree(['/login'], {
    queryParams: { returnUrl: state.url }
  });
};

// Role-based guard
export const adminGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  
  return authService.hasRole('admin');
};

// Async guard with permission check
export const permissionGuard: CanActivateFn = async (route, state) => {
  const authService = inject(AuthService);
  const requiredPermission = route.data['permission'];
  
  return await authService.hasPermission(requiredPermission);
};

// Deactivation guard for unsaved changes
export const unsavedChangesGuard: CanDeactivateFn<any> = (component) => {
  if (component.hasUnsavedChanges && component.hasUnsavedChanges()) {
    return confirm('You have unsaved changes. Do you want to leave?');
  }
  return true;
};

// Feature flag guard
export const featureFlagGuard: CanMatchFn = (route, segments) => {
  const featureService = inject(FeatureService);
  const featureName = route.data?.['feature'];
  
  return featureService.isEnabled(featureName);
};

Data Resolvers

Pre-route-activation data loading mechanism for fetching required data before component initialization.

/**
 * Functional resolver for pre-loading route data
 * @param route Snapshot of activated route
 * @param state Snapshot of router state
 * @returns Promise/Observable of resolved data or redirect command
 */
type ResolveFn<T> = (
  route: ActivatedRouteSnapshot, 
  state: RouterStateSnapshot
) => MaybeAsync<T | RedirectCommand>;

/**
 * Map of resolver functions keyed by data property name
 */
type ResolveData = {[key: string | symbol]: ResolveFn<unknown> | DeprecatedResolve};

Resolver Examples:

import { inject } from '@angular/core';
import { ResolveFn, RedirectCommand, Router } from '@angular/router';
import { UserService } from './user.service';
import { EMPTY, catchError } from 'rxjs';

// Simple data resolver
export const userResolver: ResolveFn<User> = (route, state) => {
  const userService = inject(UserService);
  const userId = route.paramMap.get('id')!;
  
  return userService.getUser(userId);
};

// Resolver with error handling and redirect
export const userWithErrorHandlingResolver: ResolveFn<User | null> = (route, state) => {
  const userService = inject(UserService);
  const router = inject(Router);
  const userId = route.paramMap.get('id')!;
  
  return userService.getUser(userId).pipe(
    catchError(error => {
      if (error.status === 404) {
        // Redirect to not found page
        const urlTree = router.createUrlTree(['/not-found']);
        return new RedirectCommand(urlTree);
      }
      return EMPTY;
    })
  );
};

// Multiple data resolver
export const dashboardDataResolver: ResolveFn<DashboardData> = async (route, state) => {
  const userService = inject(UserService);
  const statsService = inject(StatsService);
  
  const [user, stats] = await Promise.all([
    userService.getCurrentUser().toPromise(),
    statsService.getStats().toPromise()
  ]);
  
  return { user, stats };
};

// Conditional resolver
export const conditionalDataResolver: ResolveFn<any> = (route, state) => {
  const dataService = inject(DataService);
  const tab = route.queryParamMap.get('tab');
  
  switch (tab) {
    case 'profile': return dataService.getUserProfile();
    case 'settings': return dataService.getUserSettings();
    default: return { message: 'Select a tab' };
  }
};

Guard Utilities

Utility functions for converting class-based guards to functional guards.

/**
 * Convert CanActivate class providers to functional guards
 * @param providers Array of CanActivate class providers
 * @returns Array of CanActivateFn functions
 */
function mapToCanActivate(providers: Array<Type<CanActivate>>): CanActivateFn[];

/**
 * Convert CanActivateChild class providers to functional guards
 * @param providers Array of CanActivateChild class providers
 * @returns Array of CanActivateChildFn functions
 */
function mapToCanActivateChild(providers: Array<Type<CanActivateChild>>): CanActivateChildFn[];

/**
 * Convert CanDeactivate class providers to functional guards
 * @param providers Array of CanDeactivate class providers
 * @returns Array of CanDeactivateFn functions
 */
function mapToCanDeactivate<T>(providers: Array<Type<CanDeactivate<T>>>): CanDeactivateFn<T>[];

/**
 * Convert CanMatch class providers to functional guards
 * @param providers Array of CanMatch class providers
 * @returns Array of CanMatchFn functions
 */
function mapToCanMatch(providers: Array<Type<CanMatch>>): CanMatchFn[];

/**
 * Convert Resolve class provider to functional resolver
 * @param provider Resolve class provider
 * @returns ResolveFn function
 */
function mapToResolve<T>(provider: Type<Resolve<T>>): ResolveFn<T>;

URL Matching

Custom URL matching for complex route patterns.

/**
 * Custom URL matcher function for complex route patterns
 * @param segments URL segments to match
 * @param group URL segment group being matched
 * @param route Route configuration
 * @returns Match result with consumed segments and parameters, or null if no match
 */
type UrlMatcher = (
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route
) => UrlMatchResult | null;

/**
 * Result of URL matching operation
 */
interface UrlMatchResult {
  /** Segments consumed by the matcher */
  consumed: UrlSegment[];
  /** Extracted positional parameters */
  posParams?: {[name: string]: UrlSegment};
}

URL Matcher Examples:

import { UrlMatcher, UrlSegment, UrlSegmentGroup, Route } from '@angular/router';

// Custom matcher for file downloads
export const fileDownloadMatcher: UrlMatcher = (
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route
): UrlMatchResult | null => {
  // Match paths like /download/file.pdf or /download/image.jpg
  if (segments.length >= 2 && segments[0].path === 'download') {
    const filename = segments[1].path;
    const extension = filename.split('.').pop();
    
    if (['pdf', 'jpg', 'png', 'doc'].includes(extension)) {
      return {
        consumed: segments.slice(0, 2),
        posParams: {
          filename: segments[1]
        }
      };
    }
  }
  return null;
};

// Date-based matcher
export const dateMatcher: UrlMatcher = (segments, group, route) => {
  // Match paths like /archive/2023/12/25
  if (segments.length >= 4 && segments[0].path === 'archive') {
    const year = parseInt(segments[1].path);
    const month = parseInt(segments[2].path);
    const day = parseInt(segments[3].path);
    
    if (year > 2000 && month >= 1 && month <= 12 && day >= 1 && day <= 31) {
      return {
        consumed: segments.slice(0, 4),
        posParams: {
          year: segments[1],
          month: segments[2],
          day: segments[3]
        }
      };
    }
  }
  return null;
};

// Use custom matcher in route configuration
const routes: Routes = [
  {
    matcher: fileDownloadMatcher,
    component: FileDownloadComponent
  },
  {
    matcher: dateMatcher,
    component: ArchiveComponent
  }
];

Redirect Functions

Dynamic redirect logic for complex redirection scenarios.

/**
 * Function-based redirect with access to route data
 * @param redirectData Route data for redirect logic
 * @returns Promise/Observable of redirect URL
 */
type RedirectFunction = (
  redirectData: Pick<ActivatedRouteSnapshot, 'routeConfig' | 'url' | 'params' | 'queryParams' | 'fragment' | 'data' | 'outlet' | 'title'>
) => MaybeAsync<string | UrlTree>;

Redirect Function Examples:

import { RedirectFunction } from '@angular/router';

// Conditional redirect based on user role
export const dashboardRedirect: RedirectFunction = (redirectData) => {
  const userRole = getCurrentUserRole(); // Your service call
  
  switch (userRole) {
    case 'admin': return '/admin-dashboard';
    case 'user': return '/user-dashboard';
    default: return '/login';
  }
};

// Redirect preserving query parameters
export const homeRedirect: RedirectFunction = (redirectData) => {
  const queryString = new URLSearchParams(redirectData.queryParams).toString();
  return queryString ? `/home?${queryString}` : '/home';
};

// Async redirect with service call
export const dynamicRedirect: RedirectFunction = async (redirectData) => {
  const configService = inject(ConfigService);
  const defaultRoute = await configService.getDefaultRoute().toPromise();
  return defaultRoute || '/home';
};

// Use in route configuration
const routes: Routes = [
  {
    path: 'dashboard',
    redirectTo: dashboardRedirect
  },
  {
    path: 'old-home',
    redirectTo: homeRedirect
  }
];

Types

type MaybeAsync<T> = T | Observable<T> | Promise<T>;
type GuardResult = boolean | UrlTree | RedirectCommand;
type Data = {[key: string | symbol]: any};
type LoadChildren = LoadChildrenCallback;

type RunGuardsAndResolvers = 
  | 'pathParamsChange' 
  | 'pathParamsOrQueryParamsChange' 
  | 'paramsChange' 
  | 'paramsOrQueryParamsChange' 
  | 'always' 
  | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);

/**
 * Callback function for loading child routes
 */
type LoadChildrenCallback = () => 
  | Type<any> 
  | NgModuleFactory<any> 
  | Routes 
  | Observable<Type<any> | NgModuleFactory<any> | Routes> 
  | Promise<NgModuleFactory<any> | Type<any> | Routes>;

/**
 * Command instructing Router to redirect rather than continue processing
 */
class RedirectCommand {
  constructor(
    redirectTo: UrlTree, 
    navigationBehaviorOptions?: NavigationBehaviorOptions
  );
}

// Deprecated guard interfaces (use functional guards instead)
interface CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
}

interface CanActivateChild {
  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
}

interface CanDeactivate<T> {
  canDeactivate(
    component: T,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot
  ): MaybeAsync<GuardResult>;
}

interface CanMatch {
  canMatch(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
}

interface CanLoad {
  canLoad(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
}

interface Resolve<T> {
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<T | RedirectCommand>;
}

type DeprecatedGuard = any;
type DeprecatedResolve = any;