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

advanced-features.mddocs/

Advanced Features

Advanced routing features including preloading strategies, route reuse, title management, and module configuration. Provides sophisticated routing capabilities for complex applications.

Capabilities

Router Configuration

Router setup with providers and feature configuration for standalone and NgModule-based applications.

/**
 * Sets up providers for Router functionality
 * @param routes Route configuration array
 * @param features Additional router features
 * @returns Environment providers for router
 */
function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders;

/**
 * Registers DI provider for set of routes (deprecated, use provideRouter)
 * @param routes Route configuration array
 * @returns Provider array
 */
function provideRoutes(routes: Routes): Provider[];

/**
 * NgModule for router functionality
 */
class RouterModule {
  /**
   * Creates module with router service for root module
   * @param routes Route configuration
   * @param config Optional extra configuration
   * @returns Module with providers
   */
  static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule>;
  
  /**
   * Creates module for feature modules
   * @param routes Route configuration
   * @returns Module with providers
   */
  static forChild(routes: Routes): ModuleWithProviders<RouterModule>;
}

Router Configuration Examples:

// Standalone application setup
import { bootstrapApplication } from '@angular/platform-browser';
import { 
  provideRouter, 
  withPreloading, 
  withComponentInputBinding,
  withViewTransitions,
  PreloadAllModules 
} from '@angular/router';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes,
      withPreloading(PreloadAllModules),
      withComponentInputBinding(),
      withViewTransitions({
        skipInitialTransition: true
      })
    )
  ]
});

// NgModule-based setup
@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      preloadingStrategy: PreloadAllModules,
      enableTracing: false,
      bindToComponentInputs: true,
      enableViewTransitions: true
    })
  ],
  providers: []
})
export class AppModule {}

// Feature module setup
@NgModule({
  imports: [
    RouterModule.forChild(featureRoutes)
  ]
})
export class FeatureModule {}

Router Features

Feature functions for configuring specific router capabilities.

/**
 * Configure in-memory scrolling behavior
 * @param options Scrolling configuration options
 * @returns In-memory scrolling feature
 */
function withInMemoryScrolling(options?: InMemoryScrollingOptions): InMemoryScrollingFeature;

/**
 * Enable blocking initial navigation
 * @returns Enabled blocking initial navigation feature
 */
function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;

/**
 * Disable initial navigation
 * @returns Disabled initial navigation feature
 */
function withDisabledInitialNavigation(): DisabledInitialNavigationFeature;

/**
 * Enable debug tracing for navigation
 * @returns Debug tracing feature
 */
function withDebugTracing(): DebugTracingFeature;

/**
 * Configure preloading strategy
 * @param preloadingStrategy Strategy class for preloading
 * @returns Preloading feature
 */
function withPreloading(preloadingStrategy: Type<PreloadingStrategy>): PreloadingFeature;

/**
 * Configure router options
 * @param options Router configuration options
 * @returns Router configuration feature
 */
function withRouterConfig(options: RouterConfigOptions): RouterConfigurationFeature;

/**
 * Enable hash-based location strategy
 * @returns Router hash location feature
 */
function withHashLocation(): RouterHashLocationFeature;

/**
 * Configure navigation error handler
 * @param handler Error handler function
 * @returns Navigation error handler feature
 */
function withNavigationErrorHandler(
  handler: (error: NavigationError) => unknown | RedirectCommand
): NavigationErrorHandlerFeature;

/**
 * Enable component input binding from route data
 * @returns Component input binding feature
 */
function withComponentInputBinding(): ComponentInputBindingFeature;

/**
 * Enable view transitions for navigation
 * @param options View transitions configuration
 * @returns View transitions feature
 */
function withViewTransitions(options?: ViewTransitionsFeatureOptions): ViewTransitionsFeature;

Router Features Usage Examples:

import { 
  provideRouter,
  withPreloading,
  withInMemoryScrolling,
  withDebugTracing,
  withComponentInputBinding,
  withViewTransitions,
  withNavigationErrorHandler,
  PreloadAllModules
} from '@angular/router';

// Complete configuration example
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes,
      // Preload all lazy-loaded modules
      withPreloading(PreloadAllModules),
      
      // Configure scrolling behavior
      withInMemoryScrolling({
        scrollPositionRestoration: 'top',
        anchorScrolling: 'enabled'
      }),
      
      // Enable debug tracing (development only)
      withDebugTracing(),
      
      // Bind route data to component inputs
      withComponentInputBinding(),
      
      // Enable view transitions
      withViewTransitions({
        skipInitialTransition: true,
        onViewTransitionCreated: (transitionInfo) => {
          console.log('View transition created:', transitionInfo);
        }
      }),
      
      // Custom error handling
      withNavigationErrorHandler((error) => {
        console.error('Navigation error:', error);
        // Return redirect command or handle error
        if (error.message.includes('access denied')) {
          return new RedirectCommand(router.createUrlTree(['/login']));
        }
        return null;
      })
    )
  ]
});

// Custom preloading strategy
@Injectable({
  providedIn: 'root'
})
export class CustomPreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    // Only preload routes marked for preloading
    if (route.data && route.data['preload']) {
      console.log('Preloading route:', route.path);
      return fn();
    }
    
    // Don't preload this route
    return of(null);
  }
}

// Use custom preloading strategy
provideRouter(routes,
  withPreloading(CustomPreloadingStrategy)
);

Preloading Strategies

Strategies for preloading lazy-loaded routes to improve navigation performance.

/**
 * Abstract base class for preloading strategies
 */
abstract class PreloadingStrategy {
  /**
   * Preload a route
   * @param route Route to preload
   * @param fn Function to call for preloading
   * @returns Observable of preload result
   */
  abstract preload(route: Route, fn: () => Observable<any>): Observable<any>;
}

/**
 * Preloading strategy that preloads all modules as quickly as possible
 * Injectable service provided at root level
 */
class PreloadAllModules implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any>;
}

/**
 * Preloading strategy that does not preload any modules (default)
 * Injectable service provided at root level
 */
class NoPreloading implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any>;
}

/**
 * Service that optimistically loads router configurations
 * Injectable service provided at root level
 */
class RouterPreloader {
  /**
   * Sets up preloading listeners
   */
  setUpPreloading(): void;
  
  /**
   * Triggers preloading
   * @returns Observable of preload results
   */
  preload(): Observable<any>;
}

Preloading Strategy Examples:

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';

// Custom selective preloading strategy
@Injectable({
  providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    // Check if route should be preloaded
    if (route.data && route.data['preload'] === true) {
      console.log(`Preloading route: ${route.path}`);
      return fn();
    }
    
    // Don't preload
    return of(null);
  }
}

// Network-aware preloading strategy
@Injectable({
  providedIn: 'root'
})
export class NetworkAwarePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    // Check network connection
    const connection = (navigator as any).connection;
    
    if (connection) {
      // Only preload on fast connections
      if (connection.effectiveType === '4g') {
        return fn();
      }
      
      // Delay preloading on slower connections
      if (connection.effectiveType === '3g') {
        return fn().pipe(delay(2000));
      }
    }
    
    // Default behavior for unknown connections
    return fn();
  }
}

// Time-based preloading strategy
@Injectable({
  providedIn: 'root'
})
export class DelayedPreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    const delayMs = route.data?.['preloadDelay'] || 0;
    
    if (delayMs > 0) {
      console.log(`Preloading route ${route.path} after ${delayMs}ms delay`);
      return of(null).pipe(
        delay(delayMs),
        switchMap(() => fn())
      );
    }
    
    return fn();
  }
}

// Route configuration with preloading hints
const routes: Routes = [
  {
    path: 'feature1',
    loadChildren: () => import('./feature1/feature1.module').then(m => m.Feature1Module),
    data: { preload: true }
  },
  {
    path: 'feature2',
    loadChildren: () => import('./feature2/feature2.module').then(m => m.Feature2Module),
    data: { preload: true, preloadDelay: 5000 }
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    data: { preload: false } // Don't preload admin module
  }
];

Route Reuse Strategy

Strategy for controlling route component lifecycle and reuse for performance optimization.

/**
 * Abstract base class for route reuse strategies
 * Injectable service provided at root level
 */
abstract class RouteReuseStrategy {
  /**
   * Determines if route component should be detached for reuse
   * @param route Activated route snapshot
   * @returns True if component should be detached
   */
  abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
  
  /**
   * Store detached route handle
   * @param route Activated route snapshot
   * @param handle Detached route handle or null
   */
  abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void;
  
  /**
   * Determines if route should be reattached
   * @param route Activated route snapshot
   * @returns True if route should be reattached
   */
  abstract shouldAttach(route: ActivatedRouteSnapshot): boolean;
  
  /**
   * Retrieve stored route handle
   * @param route Activated route snapshot
   * @returns Stored route handle or null
   */
  abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
  
  /**
   * Determines if route should be reused
   * @param future Future activated route
   * @param curr Current activated route
   * @returns True if route should be reused
   */
  abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
}

/**
 * Base implementation that only reuses routes when router configs are identical
 */
abstract class BaseRouteReuseStrategy implements RouteReuseStrategy {
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
}

/**
 * Default route reuse strategy extending BaseRouteReuseStrategy
 * Injectable service provided at root level
 */
class DefaultRouteReuseStrategy extends BaseRouteReuseStrategy {
  shouldDetach(route: ActivatedRouteSnapshot): boolean;
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void;
  shouldAttach(route: ActivatedRouteSnapshot): boolean;
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
}

Route Reuse Strategy Examples:

import { Injectable } from '@angular/core';
import { 
  RouteReuseStrategy, 
  ActivatedRouteSnapshot, 
  DetachedRouteHandle 
} from '@angular/router';

// Custom route reuse strategy for tab-like behavior
@Injectable({
  providedIn: 'root'
})
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  private storedRoutes = new Map<string, DetachedRouteHandle>();
  
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // Detach routes marked for reuse
    return route.data && route.data['reuseRoute'] === true;
  }
  
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
    if (handle) {
      const key = this.getRouteKey(route);
      this.storedRoutes.set(key, handle);
      console.log(`Stored route: ${key}`);
    }
  }
  
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const key = this.getRouteKey(route);
    return this.storedRoutes.has(key);
  }
  
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    const key = this.getRouteKey(route);
    const handle = this.storedRoutes.get(key) || null;
    
    if (handle) {
      console.log(`Retrieved route: ${key}`);
    }
    
    return handle;
  }
  
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    // Reuse if route config is the same
    return future.routeConfig === curr.routeConfig;
  }
  
  private getRouteKey(route: ActivatedRouteSnapshot): string {
    return route.routeConfig?.path || '';
  }
}

// Provide custom route reuse strategy
@NgModule({
  providers: [
    { provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }
  ]
})
export class AppModule {}

// Route configuration with reuse hints
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    data: { reuseRoute: true }
  },
  {
    path: 'reports',
    component: ReportsComponent,
    data: { reuseRoute: true }
  },
  {
    path: 'settings',
    component: SettingsComponent,
    data: { reuseRoute: false } // Always create new instance
  }
];

// Advanced reuse strategy with cleanup
@Injectable({
  providedIn: 'root'
})
export class AdvancedRouteReuseStrategy implements RouteReuseStrategy {
  private storedRoutes = new Map<string, {
    handle: DetachedRouteHandle,
    timestamp: number
  }>();
  
  private maxAge = 5 * 60 * 1000; // 5 minutes
  private maxRoutes = 10;
  
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // Clean up old routes
    this.cleanup();
    
    return route.data?.['reuseRoute'] === true;
  }
  
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
    if (handle && this.storedRoutes.size < this.maxRoutes) {
      const key = this.getRouteKey(route);
      this.storedRoutes.set(key, {
        handle,
        timestamp: Date.now()
      });
    }
  }
  
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const key = this.getRouteKey(route);
    const stored = this.storedRoutes.get(key);
    
    if (stored) {
      // Check if route is too old
      const age = Date.now() - stored.timestamp;
      if (age > this.maxAge) {
        this.storedRoutes.delete(key);
        return false;
      }
      return true;
    }
    
    return false;
  }
  
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    const key = this.getRouteKey(route);
    const stored = this.storedRoutes.get(key);
    return stored ? stored.handle : null;
  }
  
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }
  
  private getRouteKey(route: ActivatedRouteSnapshot): string {
    // Create unique key including parameters
    const path = route.routeConfig?.path || '';
    const params = Object.keys(route.params)
      .sort()
      .map(key => `${key}:${route.params[key]}`)
      .join('|');
    
    return params ? `${path}?${params}` : path;
  }
  
  private cleanup(): void {
    const now = Date.now();
    
    for (const [key, stored] of this.storedRoutes.entries()) {
      if (now - stored.timestamp > this.maxAge) {
        this.storedRoutes.delete(key);
      }
    }
  }
}

Title Strategy

Strategy for managing page titles based on route configuration and navigation.

/**
 * Abstract base class for title strategies
 * Injectable service provided at root level
 */
abstract class TitleStrategy {
  /**
   * Update page title based on router state
   * @param snapshot Router state snapshot
   */
  abstract updateTitle(snapshot: RouterStateSnapshot): void;
  
  /**
   * Build title from router state snapshot
   * @param snapshot Router state snapshot
   * @returns Built title string or undefined
   */
  buildTitle(snapshot: RouterStateSnapshot): string | undefined;
  
  /**
   * Get resolved title for specific route
   * @param snapshot Activated route snapshot
   * @returns Resolved title value
   */
  getResolvedTitleForRoute(snapshot: ActivatedRouteSnapshot): any;
}

/**
 * Default title strategy extending TitleStrategy
 * Injectable service provided at root level
 */
class DefaultTitleStrategy extends TitleStrategy {
  updateTitle(snapshot: RouterStateSnapshot): void;
}

Title Strategy Examples:

import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { 
  TitleStrategy, 
  RouterStateSnapshot, 
  ActivatedRouteSnapshot 
} from '@angular/router';

// Custom title strategy with prefix and suffix
@Injectable({
  providedIn: 'root'
})
export class CustomTitleStrategy extends TitleStrategy {
  constructor(private title: Title) {
    super();
  }
  
  updateTitle(routerState: RouterStateSnapshot): void {
    const title = this.buildTitle(routerState);
    
    if (title) {
      // Add app name prefix
      this.title.setTitle(`MyApp - ${title}`);
    } else {
      // Default title
      this.title.setTitle('MyApp');
    }
  }
  
  override buildTitle(routerState: RouterStateSnapshot): string | undefined {
    // Walk through route tree to build hierarchical title
    const titles: string[] = [];
    let route = routerState.root;
    
    while (route) {
      const routeTitle = this.getResolvedTitleForRoute(route);
      if (routeTitle) {
        titles.push(routeTitle);
      }
      
      route = route.firstChild;
    }
    
    return titles.length > 0 ? titles.join(' | ') : undefined;
  }
}

// Advanced title strategy with breadcrumbs
@Injectable({
  providedIn: 'root'
})
export class BreadcrumbTitleStrategy extends TitleStrategy {
  constructor(private title: Title) {
    super();
  }
  
  updateTitle(routerState: RouterStateSnapshot): void {
    const breadcrumbs = this.buildBreadcrumbs(routerState);
    const title = breadcrumbs.join(' › ');
    
    if (title) {
      this.title.setTitle(`${title} - MyApp`);
    } else {
      this.title.setTitle('MyApp');
    }
    
    // Also emit breadcrumbs for navigation component
    this.emitBreadcrumbs(breadcrumbs);
  }
  
  private buildBreadcrumbs(routerState: RouterStateSnapshot): string[] {
    const breadcrumbs: string[] = [];
    let route: ActivatedRouteSnapshot | null = routerState.root;
    
    while (route) {
      // Check for breadcrumb data
      const breadcrumb = route.data['breadcrumb'];
      const title = this.getResolvedTitleForRoute(route);
      
      if (breadcrumb) {
        breadcrumbs.push(breadcrumb);
      } else if (title) {
        breadcrumbs.push(title);
      }
      
      route = route.firstChild;
    }
    
    return breadcrumbs.filter(b => b); // Remove empty breadcrumbs
  }
  
  private emitBreadcrumbs(breadcrumbs: string[]): void {
    // Emit to a service or subject for breadcrumb component
    // this.breadcrumbService.updateBreadcrumbs(breadcrumbs);
  }
}

// Route configuration with title and breadcrumb data
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    title: 'Dashboard',
    data: { breadcrumb: 'Home' }
  },
  {
    path: 'users',
    component: UsersComponent,
    title: 'Users',
    data: { breadcrumb: 'Users' },
    children: [
      {
        path: ':id',
        component: UserDetailComponent,
        title: userTitleResolver,
        data: { breadcrumb: 'User Details' }
      }
    ]
  },
  {
    path: 'settings',
    component: SettingsComponent,
    title: 'Settings',
    data: { breadcrumb: 'Settings' },
    children: [
      {
        path: 'profile',
        component: ProfileSettingsComponent,
        title: 'Profile Settings',
        data: { breadcrumb: 'Profile' }
      }
    ]
  }
];

// Title resolver function (add necessary imports)
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { map, catchError, of } from 'rxjs';

export const userTitleResolver: ResolveFn<string> = (route, state) => {
  const userService = inject(UserService);
  const userId = route.paramMap.get('id')!;
  
  return userService.getUser(userId).pipe(
    map(user => `${user.firstName} ${user.lastName}`),
    catchError(() => of('User'))
  );
};

// Provide custom title strategy
@NgModule({
  providers: [
    { provide: TitleStrategy, useClass: CustomTitleStrategy }
  ]
})
export class AppModule {}

Configuration Options

Advanced configuration options for router behavior customization.

/**
 * Extra options for router configuration
 */
interface ExtraOptions extends InMemoryScrollingOptions, RouterConfigOptions {
  /** Enable tracing of navigation events */
  enableTracing?: boolean;
  /** Use hash-based location strategy */
  useHash?: boolean;
  /** Initial navigation configuration */
  initialNavigation?: InitialNavigation;
  /** Bind route data to component inputs */
  bindToComponentInputs?: boolean;
  /** Enable view transitions */
  enableViewTransitions?: boolean;
  /** Custom navigation error handler */
  errorHandler?: (error: NavigationError) => any;
  /** Preloading strategy */
  preloadingStrategy?: Type<PreloadingStrategy>;
  /** Scroll offset configuration */
  scrollOffset?: [number, number] | (() => [number, number]);
}

/**
 * Router configuration options
 */
interface RouterConfigOptions {
  /** How to handle canceled navigation */
  canceledNavigationResolution?: 'replace' | 'computed';
  /** How to handle same URL navigation */
  onSameUrlNavigation?: 'reload' | 'ignore';
  /** Parameters inheritance strategy */
  paramsInheritanceStrategy?: 'emptyOnly' | 'always';
  /** URL update strategy */
  urlUpdateStrategy?: 'deferred' | 'eager';
  /** Default query parameters handling */
  defaultQueryParamsHandling?: QueryParamsHandling;
  /** Resolve navigation promise on error */
  resolveNavigationPromiseOnError?: boolean;
}

/**
 * In-memory scrolling options
 */
interface InMemoryScrollingOptions {
  /** Anchor scrolling behavior */
  anchorScrolling?: 'disabled' | 'enabled';
  /** Scroll position restoration */
  scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';
}

type InitialNavigation = 'disabled' | 'enabled' | 'enabledBlocking' | 'enabledNonBlocking';

Types

type DetachedRouteHandle = {};
type RouterFeatures = PreloadingFeature | InMemoryScrollingFeature | 
                     EnabledBlockingInitialNavigationFeature | DisabledInitialNavigationFeature |
                     DebugTracingFeature | RouterConfigurationFeature | RouterHashLocationFeature |
                     NavigationErrorHandlerFeature | ComponentInputBindingFeature | ViewTransitionsFeature;

interface ViewTransitionsFeatureOptions {
  /** Skip initial transition */
  skipInitialTransition?: boolean;
  /** Callback when view transition is created */
  onViewTransitionCreated?: (transitionInfo: ViewTransitionInfo) => void;
}

interface ViewTransitionInfo {
  /** View transition object */
  transition: ViewTransition;
  /** Source route snapshot */
  from: ActivatedRouteSnapshot;
  /** Target route snapshot */
  to: ActivatedRouteSnapshot;
}

/**
 * Constants and tokens
 */
const VERSION: Version;
const PRIMARY_OUTLET = 'primary';

/**
 * Injection tokens
 */
const ROUTES: InjectionToken<Route[][]>;
const ROUTER_CONFIGURATION: InjectionToken<ExtraOptions>;
const ROUTER_INITIALIZER: InjectionToken<() => void>;
const ROUTER_OUTLET_DATA: InjectionToken<unknown>;

Outlet Context Management

Advanced APIs for managing router outlet contexts, primarily used for custom outlet implementations and complex routing scenarios.

/**
 * Service for managing outlet contexts
 * Injectable service that tracks outlet contexts for child routes
 */
@Injectable({providedIn: 'root'})
class ChildrenOutletContexts {
  /** 
   * Get or create context for outlet
   * @param childName Name of the child outlet
   * @returns OutletContext for the child
   */
  getOrCreateContext(childName: string): OutletContext;
  
  /** 
   * Get existing context for outlet
   * @param childName Name of the child outlet
   * @returns OutletContext or null if not found
   */
  getContext(childName: string): OutletContext | null;
  
  /**
   * Remove context for outlet
   * @param childName Name of the child outlet
   */
  removeContext(childName: string): void;
}

/**
 * Context information for router outlets
 * Holds state and context for individual outlets
 */
class OutletContext {
  /** Reference to the outlet */
  outlet: RouterOutletContract | null;
  /** Route associated with outlet */
  route: ActivatedRoute | null;
  /** Injector for outlet */
  injector: Injector | null;
  /** Child contexts */
  children: ChildrenOutletContexts;
  /** Attachment reference */
  attachRef: ComponentRef<any> | null;
}

/**
 * Base route reuse strategy with default implementation
 * Extends RouteReuseStrategy with common reuse logic
 */
abstract class BaseRouteReuseStrategy implements RouteReuseStrategy {
  /** Whether routes should be reused (default implementation) */
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
  
  abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
  abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void;  
  abstract shouldAttach(route: ActivatedRouteSnapshot): boolean;
  abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
}

Outlet Context Usage Examples:

import { Injectable, inject } from '@angular/core';
import { ChildrenOutletContexts, OutletContext } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class CustomOutletManager {
  private contexts = inject(ChildrenOutletContexts);
  
  // Get context for specific outlet
  getOutletContext(outletName: string): OutletContext | null {
    return this.contexts.getContext(outletName);
  }
  
  // Check if outlet is active
  isOutletActive(outletName: string): boolean {
    const context = this.contexts.getContext(outletName);
    return context && context.outlet && context.outlet.isActivated;
  }
  
  // Get component from outlet
  getOutletComponent(outletName: string): any {
    const context = this.contexts.getContext(outletName);
    return context?.outlet?.component || null;
  }
}

// Custom route reuse strategy extending BaseRouteReuseStrategy
@Injectable({
  providedIn: 'root'
})
export class CustomRouteReuseStrategy extends BaseRouteReuseStrategy {
  private storedRoutes = new Map<string, DetachedRouteHandle>();
  
  // Only cache specific routes
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data['reuseRoute'] === true;
  }
  
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const key = this.createKey(route);
    this.storedRoutes.set(key, handle);
  }
  
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const key = this.createKey(route);
    return this.storedRoutes.has(key);
  }
  
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    const key = this.createKey(route);
    return this.storedRoutes.get(key) || null;
  }
  
  private createKey(route: ActivatedRouteSnapshot): string {
    return route.routeConfig?.path || '';
  }
}