URL parsing, tree representation, serialization, and URL management utilities. Provides comprehensive URL manipulation capabilities for advanced routing scenarios.
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
});
}
}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;
}
}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;
};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;
}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 }
]
});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
});
}
}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;
}