66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { addLeadingSlash , stripTrailingSlash } from '../utils/url' ;
9+ import { addLeadingSlash } from '../utils/url' ;
1010import { RenderMode } from './route-config' ;
1111
1212/**
@@ -78,13 +78,6 @@ export interface RouteTreeNodeMetadata {
7878 * The `AdditionalMetadata` type parameter allows for extending the node metadata with custom data.
7979 */
8080interface RouteTreeNode < AdditionalMetadata extends Record < string , unknown > > {
81- /**
82- * The index indicating the order in which the route was inserted into the tree.
83- * This index helps determine the priority of routes during matching, with lower indexes
84- * indicating earlier inserted routes.
85- */
86- insertionIndex : number ;
87-
8881 /**
8982 * A map of child nodes, keyed by their corresponding route segment or wildcard.
9083 */
@@ -110,13 +103,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
110103 */
111104 private readonly root = this . createEmptyRouteTreeNode ( ) ;
112105
113- /**
114- * A counter that tracks the order of route insertion.
115- * This ensures that routes are matched in the order they were defined,
116- * with earlier routes taking precedence.
117- */
118- private insertionIndexCounter = 0 ;
119-
120106 /**
121107 * Inserts a new route into the route tree.
122108 * The route is broken down into segments, and each segment is added to the tree.
@@ -128,29 +114,28 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
128114 insert ( route : string , metadata : RouteTreeNodeMetadataWithoutRoute & AdditionalMetadata ) : void {
129115 let node = this . root ;
130116 const segments = this . getPathSegments ( route ) ;
131- const normalizedSegments : string [ ] = [ ] ;
132117
133- for ( const segment of segments ) {
134- // Replace parameterized segments (e.g., :id) with a wildcard (*) for matching
135- const normalizedSegment = segment [ 0 ] === ':' ? '*' : segment ;
136- let childNode = node . children . get ( normalizedSegment ) ;
118+ for ( let index = 0 ; index < segments . length ; index ++ ) {
119+ let segment = segments [ index ] ;
120+ if ( segment [ 0 ] === ':' ) {
121+ // Replace parameterized segments (e.g., :id) with a wildcard (*) for matching
122+ segments [ index ] = segment = '*' ;
123+ }
137124
125+ let childNode = node . children . get ( segment ) ;
138126 if ( ! childNode ) {
139127 childNode = this . createEmptyRouteTreeNode ( ) ;
140- node . children . set ( normalizedSegment , childNode ) ;
128+ node . children . set ( segment , childNode ) ;
141129 }
142130
143131 node = childNode ;
144- normalizedSegments . push ( normalizedSegment ) ;
145132 }
146133
147134 // At the leaf node, store the full route and its associated metadata
148135 node . metadata = {
149136 ...metadata ,
150- route : addLeadingSlash ( normalizedSegments . join ( '/' ) ) ,
137+ route : addLeadingSlash ( segments . join ( '/' ) ) ,
151138 } ;
152-
153- node . insertionIndex = this . insertionIndexCounter ++ ;
154139 }
155140
156141 /**
@@ -222,7 +207,7 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
222207 * @returns An array of path segments.
223208 */
224209 private getPathSegments ( route : string ) : string [ ] {
225- return stripTrailingSlash ( route ) . split ( '/' ) ;
210+ return route . split ( '/' ) . filter ( Boolean ) ;
226211 }
227212
228213 /**
@@ -232,74 +217,48 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
232217 * This function prioritizes exact segment matches first, followed by wildcard matches (`*`),
233218 * and finally deep wildcard matches (`**`) that consume all segments.
234219 *
235- * @param remainingSegments - The remaining segments of the route path to match.
236- * @param node - The current node in the route tree to start traversal from.
220+ * @param segments - The array of route path segments to match against the route tree.
221+ * @param node - The current node in the route tree to start traversal from. Defaults to the root node.
222+ * @param currentIndex - The index of the segment in `remainingSegments` currently being matched.
223+ * Defaults to `0` (the first segment).
237224 *
238225 * @returns The node that best matches the remaining segments or `undefined` if no match is found.
239226 */
240227 private traverseBySegments (
241- remainingSegments : string [ ] ,
228+ segments : string [ ] ,
242229 node = this . root ,
230+ currentIndex = 0 ,
243231 ) : RouteTreeNode < AdditionalMetadata > | undefined {
244- const { metadata, children } = node ;
245-
246- // If there are no remaining segments and the node has metadata, return this node
247- if ( ! remainingSegments . length ) {
248- return metadata ? node : node . children . get ( '**' ) ;
232+ if ( currentIndex >= segments . length ) {
233+ return node . metadata ? node : node . children . get ( '**' ) ;
249234 }
250235
251- // If the node has no children, end the traversal
252- if ( ! children . size ) {
253- return ;
236+ if ( ! node . children . size ) {
237+ return undefined ;
254238 }
255239
256- const [ segment , ...restSegments ] = remainingSegments ;
257- let currentBestMatchNode : RouteTreeNode < AdditionalMetadata > | undefined ;
258-
259- // 1. Exact segment match
260- const exactMatchNode = node . children . get ( segment ) ;
261- currentBestMatchNode = this . getHigherPriorityNode (
262- currentBestMatchNode ,
263- this . traverseBySegments ( restSegments , exactMatchNode ) ,
264- ) ;
240+ const segment = segments [ currentIndex ] ;
265241
266- // 2. Wildcard segment match (`*`)
267- const wildcardNode = node . children . get ( '*' ) ;
268- currentBestMatchNode = this . getHigherPriorityNode (
269- currentBestMatchNode ,
270- this . traverseBySegments ( restSegments , wildcardNode ) ,
271- ) ;
272-
273- // 3. Deep wildcard segment match (`**`)
274- const deepWildcardNode = node . children . get ( '**' ) ;
275- currentBestMatchNode = this . getHigherPriorityNode ( currentBestMatchNode , deepWildcardNode ) ;
276-
277- return currentBestMatchNode ;
278- }
279-
280- /**
281- * Compares two nodes and returns the node with higher priority based on insertion index.
282- * A node with a lower insertion index is prioritized as it was defined earlier.
283- *
284- * @param currentBestMatchNode - The current best match node.
285- * @param candidateNode - The node being evaluated for higher priority based on insertion index.
286- * @returns The node with higher priority (i.e., lower insertion index). If one of the nodes is `undefined`, the other node is returned.
287- */
288- private getHigherPriorityNode (
289- currentBestMatchNode : RouteTreeNode < AdditionalMetadata > | undefined ,
290- candidateNode : RouteTreeNode < AdditionalMetadata > | undefined ,
291- ) : RouteTreeNode < AdditionalMetadata > | undefined {
292- if ( ! candidateNode ) {
293- return currentBestMatchNode ;
242+ // 1. Attempt exact match with the current segment.
243+ const exactMatch = node . children . get ( segment ) ;
244+ if ( exactMatch ) {
245+ const match = this . traverseBySegments ( segments , exactMatch , currentIndex + 1 ) ;
246+ if ( match ) {
247+ return match ;
248+ }
294249 }
295250
296- if ( ! currentBestMatchNode ) {
297- return candidateNode ;
251+ // 2. Attempt wildcard match ('*').
252+ const wildcardMatch = node . children . get ( '*' ) ;
253+ if ( wildcardMatch ) {
254+ const match = this . traverseBySegments ( segments , wildcardMatch , currentIndex + 1 ) ;
255+ if ( match ) {
256+ return match ;
257+ }
298258 }
299259
300- return candidateNode . insertionIndex < currentBestMatchNode . insertionIndex
301- ? candidateNode
302- : currentBestMatchNode ;
260+ // 3. Attempt double wildcard match ('**').
261+ return node . children . get ( '**' ) ;
303262 }
304263
305264 /**
@@ -310,7 +269,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
310269 */
311270 private createEmptyRouteTreeNode ( ) : RouteTreeNode < AdditionalMetadata > {
312271 return {
313- insertionIndex : - 1 ,
314272 children : new Map ( ) ,
315273 } ;
316274 }
0 commit comments