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

url-handling.mddocs/

URL Handling

URL parsing, tree representation, serialization, and URL management utilities. Provides comprehensive URL manipulation capabilities for advanced routing scenarios.

Capabilities

UrlTree Class

Represents parsed URL as a tree structure with segments, query parameters, and fragment.

/**
 * Represents parsed URL as a tree structure
 */
class UrlTree {
  /** Root segment group */
  root: UrlSegmentGroup;
  /** Query parameters */
  queryParams: Params;
  /** URL fragment */
  fragment: string | null;
  /** Query parameters as ParamMap */
  queryParamMap: ParamMap;
}

UrlTree Usage Examples:

import { Router, UrlTree } from '@angular/router';

@Component({})
export class UrlExampleComponent {
  constructor(private router: Router) {}
  
  // Create URL tree
  createUserUrl(userId: number): UrlTree {
    return this.router.createUrlTree(['/users', userId], {
      queryParams: { tab: 'profile', edit: 'true' },
      fragment: 'personal-info'
    });
  }
  
  // Parse URL string
  parseUrl(url: string): UrlTree {
    return this.router.parseUrl(url);
  }
  
  // Serialize URL tree
  getUrlString(urlTree: UrlTree): string {
    return this.router.serializeUrl(urlTree);
  }
  
  // Access URL tree properties
  analyzeUrl(urlTree: UrlTree): void {
    console.log('Root segments:', urlTree.root.segments);
    console.log('Query params:', urlTree.queryParams);
    console.log('Fragment:', urlTree.fragment);
    
    // Use ParamMap for query parameters
    const paramMap = urlTree.queryParamMap;
    if (paramMap.has('tab')) {
      console.log('Tab parameter:', paramMap.get('tab'));
    }
    
    // Get all values for a parameter
    const tags = paramMap.getAll('tag');
    console.log('Tags:', tags);
  }
  
  // Navigate using URL tree
  navigateWithUrlTree(): void {
    const urlTree = this.createUserUrl(123);
    this.router.navigateByUrl(urlTree);
  }
  
  // Conditional navigation with URL trees
  conditionalNavigate(condition: boolean): void {
    const homeUrl = this.router.createUrlTree(['/home']);
    const dashboardUrl = this.router.createUrlTree(['/dashboard']);
    
    const targetUrl = condition ? dashboardUrl : homeUrl;
    this.router.navigateByUrl(targetUrl);
  }
}

// URL tree manipulation service
@Injectable({
  providedIn: 'root'
})
export class UrlManipulationService {
  constructor(private router: Router) {}
  
  // Add query parameter to current URL
  addQueryParam(key: string, value: string): UrlTree {
    const currentUrl = this.router.parseUrl(this.router.url);
    const newQueryParams = { ...currentUrl.queryParams, [key]: value };
    
    return this.router.createUrlTree([], {
      queryParams: newQueryParams,
      fragment: currentUrl.fragment
    });
  }
  
  // Remove query parameter from current URL
  removeQueryParam(key: string): UrlTree {
    const currentUrl = this.router.parseUrl(this.router.url);
    const newQueryParams = { ...currentUrl.queryParams };
    delete newQueryParams[key];
    
    return this.router.createUrlTree([], {
      queryParams: newQueryParams,
      fragment: currentUrl.fragment
    });
  }
  
  // Merge query parameters
  mergeQueryParams(newParams: Params): UrlTree {
    const currentUrl = this.router.parseUrl(this.router.url);
    const mergedParams = { ...currentUrl.queryParams, ...newParams };
    
    return this.router.createUrlTree([], {
      queryParams: mergedParams,
      fragment: currentUrl.fragment
    });
  }
}

UrlSegmentGroup Class

Represents parsed URL segment group with child segments and navigation structure.

/**
 * Represents parsed URL segment group
 */
class UrlSegmentGroup {
  /** URL segments of group */
  segments: UrlSegment[];
  /** Child segment groups */
  children: {[key: string]: UrlSegmentGroup};
  /** Parent node */
  parent: UrlSegmentGroup | null;
  /** Number of child segments (getter) */
  numberOfChildren: number;
  
  /**
   * Whether segment has child segments
   * @returns True if has children
   */
  hasChildren(): boolean;
}

UrlSegmentGroup Usage Examples:

import { Router, UrlTree, UrlSegmentGroup } from '@angular/router';

@Component({})
export class UrlSegmentAnalysisComponent {
  constructor(private router: Router) {}
  
  analyzeCurrentUrl(): void {
    const urlTree = this.router.parseUrl(this.router.url);
    this.analyzeSegmentGroup(urlTree.root);
  }
  
  analyzeSegmentGroup(group: UrlSegmentGroup, level = 0): void {
    const indent = '  '.repeat(level);
    
    console.log(`${indent}Segment Group:`);
    console.log(`${indent}  Segments:`, group.segments.map(s => s.path));
    console.log(`${indent}  Has children:`, group.hasChildren());
    console.log(`${indent}  Number of children:`, group.numberOfChildren);
    
    // Analyze segments in this group
    group.segments.forEach((segment, index) => {
      console.log(`${indent}  Segment ${index}:`, {
        path: segment.path,
        parameters: segment.parameters
      });
    });
    
    // Recursively analyze child groups
    Object.keys(group.children).forEach(outlet => {
      console.log(`${indent}  Child outlet '${outlet}':`);
      this.analyzeSegmentGroup(group.children[outlet], level + 1);
    });
  }
  
  // Navigate with complex segment structure
  navigateToComplexRoute(): void {
    // Creates URL like: /main/section/(detail:item//sidebar:menu)
    this.router.navigate([
      'main', 'section',
      {
        outlets: {
          detail: ['item'],
          sidebar: ['menu']
        }
      }
    ]);
  }
  
  // Find segment group by outlet name
  findSegmentByOutlet(urlTree: UrlTree, outletName: string): UrlSegmentGroup | null {
    return this.searchSegmentGroup(urlTree.root, outletName);
  }
  
  private searchSegmentGroup(group: UrlSegmentGroup, outletName: string): UrlSegmentGroup | null {
    if (group.children[outletName]) {
      return group.children[outletName];
    }
    
    for (const childGroup of Object.values(group.children)) {
      const found = this.searchSegmentGroup(childGroup, outletName);
      if (found) return found;
    }
    
    return null;
  }
}

UrlSegment Class

Represents single URL segment with path and matrix parameters.

/**
 * Represents single URL segment with path and matrix parameters
 */
class UrlSegment {
  /** Path part of URL segment */
  path: string;
  /** Matrix parameters */
  parameters: {[name: string]: string};
  /** Matrix parameters as ParamMap */
  parameterMap: ParamMap;
}

UrlSegment Usage Examples:

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

@Component({})
export class UrlSegmentExampleComponent {
  constructor(private router: Router) {}
  
  analyzeCurrentSegments(): void {
    const urlTree = this.router.parseUrl(this.router.url);
    const segments = urlTree.root.segments;
    
    segments.forEach((segment, index) => {
      console.log(`Segment ${index}:`, {
        path: segment.path,
        parameters: segment.parameters,
        hasParameters: Object.keys(segment.parameters).length > 0
      });
      
      // Access matrix parameters
      const paramMap = segment.parameterMap;
      paramMap.keys.forEach(key => {
        console.log(`  Parameter ${key}:`, paramMap.get(key));
      });
    });
  }
  
  // Navigate with matrix parameters
  navigateWithMatrixParams(): void {
    // Creates URL like: /products/123;color=red;size=large
    this.router.navigate([
      'products', 
      123, 
      { color: 'red', size: 'large' }
    ]);
  }
  
  // Extract segment information
  getSegmentInfo(segment: UrlSegment): any {
    return {
      path: segment.path,
      parameters: segment.parameters,
      parameterCount: Object.keys(segment.parameters).length,
      parameterNames: Object.keys(segment.parameters),
      hasColorParam: segment.parameterMap.has('color'),
      colorValue: segment.parameterMap.get('color')
    };
  }
  
  // Build URL with custom segments
  buildCustomUrl(): string {
    const segments = [
      new UrlSegment('products', {}),
      new UrlSegment('electronics', { category: 'phones' }),
      new UrlSegment('item', { id: '123', variant: 'pro' })
    ];
    
    // This would need custom URL building logic
    return segments.map(s => {
      let segmentStr = s.path;
      const params = Object.entries(s.parameters)
        .map(([key, value]) => `${key}=${value}`)
        .join(';');
      if (params) {
        segmentStr += ';' + params;
      }
      return segmentStr;
    }).join('/');
  }
}

// Custom URL segment matcher
export const customSegmentMatcher = (segments: UrlSegment[], group: any, route: any) => {
  // Match segments with specific matrix parameters
  if (segments.length >= 2) {
    const productSegment = segments[0];
    const itemSegment = segments[1];
    
    if (productSegment.path === 'products' && itemSegment.parameterMap.has('id')) {
      return {
        consumed: segments.slice(0, 2),
        posParams: {
          id: itemSegment,
          category: new UrlSegment(productSegment.parameterMap.get('category') || 'general', {})
        }
      };
    }
  }
  return null;
};

UrlSerializer Abstract Class

Serializes and deserializes URLs with customizable behavior for URL format control.

/**
 * Abstract class for URL serialization and deserialization
 * Injectable service provided at root level
 */
abstract class UrlSerializer {
  /**
   * Parse URL string into UrlTree
   * @param url String URL to parse
   * @returns UrlTree representation
   */
  abstract parse(url: string): UrlTree;
  
  /**
   * Convert UrlTree into URL string
   * @param tree UrlTree to serialize
   * @returns String representation of URL
   */
  abstract serialize(tree: UrlTree): string;
}

DefaultUrlSerializer Class

Default implementation of URL serialization with standard Angular URL format.

/**
 * Default implementation of URL serialization
 * Implements UrlSerializer
 */
class DefaultUrlSerializer implements UrlSerializer {
  /**
   * Parse URL string into UrlTree using default Angular format
   * @param url String URL to parse
   * @returns UrlTree representation
   */
  parse(url: string): UrlTree;
  
  /**
   * Convert UrlTree into URL string using default Angular format
   * @param tree UrlTree to serialize
   * @returns String representation of URL
   */
  serialize(tree: UrlTree): string;
}

URL Serializer Usage Examples:

import { Injectable } from '@angular/core';
import { UrlSerializer, UrlTree, DefaultUrlSerializer } from '@angular/router';

// Custom URL serializer for alternative URL formats
@Injectable({
  providedIn: 'root'
})
export class CustomUrlSerializer implements UrlSerializer {
  private defaultSerializer = new DefaultUrlSerializer();
  
  parse(url: string): UrlTree {
    // Custom parsing logic - example: convert dashes to slashes
    const normalizedUrl = url.replace(/-/g, '/');
    return this.defaultSerializer.parse(normalizedUrl);
  }
  
  serialize(tree: UrlTree): string {
    const defaultUrl = this.defaultSerializer.serialize(tree);
    
    // Custom serialization logic - example: convert slashes to dashes
    return defaultUrl.replace(/\//g, '-');
  }
}

// URL serializer with query parameter transformation
@Injectable({
  providedIn: 'root'
})
export class QueryTransformSerializer implements UrlSerializer {
  private defaultSerializer = new DefaultUrlSerializer();
  
  parse(url: string): UrlTree {
    // Transform query parameters before parsing
    const transformedUrl = this.transformQueryParams(url, 'parse');
    return this.defaultSerializer.parse(transformedUrl);
  }
  
  serialize(tree: UrlTree): string {
    const defaultUrl = this.defaultSerializer.serialize(tree);
    return this.transformQueryParams(defaultUrl, 'serialize');
  }
  
  private transformQueryParams(url: string, mode: 'parse' | 'serialize'): string {
    if (mode === 'serialize') {
      // Convert camelCase to kebab-case for external URLs
      return url.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
    } else {
      // Convert kebab-case to camelCase for internal processing
      return url.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
    }
  }
}

// Provide custom serializer
@NgModule({
  providers: [
    { provide: UrlSerializer, useClass: CustomUrlSerializer }
  ]
})
export class AppModule {}

// Or with standalone app
bootstrapApplication(AppComponent, {
  providers: [
    { provide: UrlSerializer, useClass: CustomUrlSerializer }
  ]
});

URL Utility Functions

Utility functions for URL manipulation and type checking.

/**
 * Type guard for UrlTree
 * @param v Value to check
 * @returns True if value is UrlTree
 */
function isUrlTree(v: any): v is UrlTree;

/**
 * Convert Params to ParamMap
 * @param params Params object to convert
 * @returns ParamMap instance
 */
function convertToParamMap(params: Params): ParamMap;

/**
 * Default URL matching function
 * @param segments URL segments to match
 * @param segmentGroup URL segment group
 * @param route Route configuration
 * @returns Match result or null
 */
function defaultUrlMatcher(
  segments: UrlSegment[], 
  segmentGroup: UrlSegmentGroup, 
  route: Route
): UrlMatchResult | null;

/**
 * Create URL tree from snapshot
 * @param relativeTo Base route snapshot
 * @param commands Navigation commands
 * @param queryParams Query parameters
 * @param fragment URL fragment
 * @returns UrlTree
 */
function createUrlTreeFromSnapshot(
  relativeTo: ActivatedRouteSnapshot,
  commands: any[],
  queryParams?: Params | null,
  fragment?: string | null
): UrlTree;

/**
 * Encode URI segment for URL
 * @param s String to encode
 * @returns Encoded string
 */
function encodeUriSegment(s: string): string;

/**
 * Encode URI query parameter for URL
 * @param s String to encode
 * @returns Encoded string
 */
function encodeUriQuery(s: string): string;

/**
 * Encode URI fragment for URL
 * @param s String to encode
 * @returns Encoded string
 */
function encodeUriFragment(s: string): string;

/**
 * Decode URI component
 * @param s String to decode
 * @returns Decoded string
 */
function decode(s: string): string;

/**
 * Decode query parameter
 * @param s String to decode
 * @returns Decoded string
 */
function decodeQuery(s: string): string;

/**
 * Check if one URL tree contains another
 * @param container Container URL tree
 * @param containee URL tree to check
 * @param options Matching options
 * @returns True if contained
 */
function containsTree(
  container: UrlTree, 
  containee: UrlTree, 
  options: IsActiveMatchOptions
): boolean;

/**
 * Compare two URL segment arrays for equality
 * @param as First segment array
 * @param bs Second segment array
 * @returns True if equal
 */
function equalSegments(as: UrlSegment[], bs: UrlSegment[]): boolean;

/**
 * Compare two URL segment arrays paths only
 * @param as First segment array
 * @param bs Second segment array
 * @returns True if paths equal
 */
function equalPath(as: UrlSegment[], bs: UrlSegment[]): boolean;

URL Utility Usage Examples:

import { 
  isUrlTree, 
  convertToParamMap, 
  defaultUrlMatcher,
  createUrlTreeFromSnapshot 
} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class UrlUtilityService {
  
  // Type checking
  processNavigationTarget(target: string | UrlTree): void {
    if (isUrlTree(target)) {
      console.log('Processing UrlTree:', target);
      this.handleUrlTree(target);
    } else {
      console.log('Processing string URL:', target);
      this.handleStringUrl(target);
    }
  }
  
  // Parameter conversion
  processParams(params: any): ParamMap {
    return convertToParamMap(params);
  }
  
  // Custom URL matching
  createProductMatcher() {
    return (segments: UrlSegment[], group: UrlSegmentGroup, route: Route) => {
      // Use default matcher as base
      const baseResult = defaultUrlMatcher(segments, group, route);
      
      if (baseResult && segments.length > 0) {
        const productId = segments[0].path;
        
        // Additional validation
        if (/^\d+$/.test(productId)) {
          return {
            consumed: baseResult.consumed,
            posParams: {
              ...baseResult.posParams,
              productId: segments[0]
            }
          };
        }
      }
      
      return null;
    };
  }
  
  // URL tree creation from snapshot
  createRelativeUrl(
    currentRoute: ActivatedRouteSnapshot, 
    targetPath: string[]
  ): UrlTree {
    return createUrlTreeFromSnapshot(
      currentRoute,
      targetPath,
      currentRoute.queryParams,
      currentRoute.fragment
    );
  }
  
  private handleUrlTree(urlTree: UrlTree): void {
    // Handle UrlTree specific logic
    console.log('Query params:', urlTree.queryParams);
    console.log('Fragment:', urlTree.fragment);
  }
  
  private handleStringUrl(url: string): void {
    // Handle string URL specific logic
    console.log('String URL length:', url.length);
  }
}

// Advanced URL manipulation
@Injectable({
  providedIn: 'root'
})
export class AdvancedUrlService {
  constructor(private router: Router) {}
  
  // Clone URL tree with modifications
  cloneUrlTreeWithChanges(
    sourceTree: UrlTree,
    modifications: {
      queryParams?: Params,
      fragment?: string | null
    }
  ): UrlTree {
    return this.router.createUrlTree([], {
      relativeTo: null,
      queryParams: modifications.queryParams || sourceTree.queryParams,
      fragment: modifications.fragment !== undefined 
        ? modifications.fragment 
        : sourceTree.fragment
    });
  }
  
  // Compare URL trees
  urlTreesEqual(tree1: UrlTree, tree2: UrlTree): boolean {
    const url1 = this.router.serializeUrl(tree1);
    const url2 = this.router.serializeUrl(tree2);
    return url1 === url2;
  }
  
  // Extract base path from URL tree
  getBasePath(urlTree: UrlTree): string[] {
    return urlTree.root.segments.map(segment => segment.path);
  }
  
  // Build URL with preserved parameters
  buildUrlPreservingParams(
    basePath: string[], 
    currentUrl: UrlTree
  ): UrlTree {
    return this.router.createUrlTree(basePath, {
      queryParams: currentUrl.queryParams,
      fragment: currentUrl.fragment
    });
  }
}

Types

type Params = {[key: string]: any};

interface ParamMap {
  /** Check if parameter exists */
  has(name: string): boolean;
  /** Get single parameter value */
  get(name: string): string | null;
  /** Get all values for parameter */
  getAll(name: string): string[];
  /** All parameter names */
  readonly keys: string[];
}

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

type UrlMatcher = (
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route
) => UrlMatchResult | null;

/**
 * Primary outlet identifier
 */
const PRIMARY_OUTLET = 'primary';

/**
 * Options for URL tree creation and navigation
 */
interface UrlCreationOptions {
  /** Route to use as base for relative navigation */
  relativeTo?: ActivatedRoute | null;
  /** Query parameters to add to URL */
  queryParams?: Params | null;
  /** URL fragment to add */
  fragment?: string | null;
  /** How to handle existing query parameters */
  queryParamsHandling?: QueryParamsHandling | null;
  /** Whether to preserve current fragment */
  preserveFragment?: boolean;
}

/**
 * Context for URL handling strategies
 */
interface UrlHandlingStrategy {
  shouldProcessUrl(url: UrlTree): boolean;
  extract(url: UrlTree): UrlTree;
  merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree;
}

/**
 * Default URL handling strategy
 */
class DefaultUrlHandlingStrategy implements UrlHandlingStrategy {
  shouldProcessUrl(url: UrlTree): boolean;
  extract(url: UrlTree): UrlTree;
  merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree;
}