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.
@@ -134,7 +120,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
134120 // Replace parameterized segments (e.g., :id) with a wildcard (*) for matching
135121 const normalizedSegment = segment [ 0 ] === ':' ? '*' : segment ;
136122 let childNode = node . children . get ( normalizedSegment ) ;
137-
138123 if ( ! childNode ) {
139124 childNode = this . createEmptyRouteTreeNode ( ) ;
140125 node . children . set ( normalizedSegment , childNode ) ;
@@ -149,8 +134,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
149134 ...metadata ,
150135 route : addLeadingSlash ( normalizedSegments . join ( '/' ) ) ,
151136 } ;
152-
153- node . insertionIndex = this . insertionIndexCounter ++ ;
154137 }
155138
156139 /**
@@ -222,7 +205,7 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
222205 * @returns An array of path segments.
223206 */
224207 private getPathSegments ( route : string ) : string [ ] {
225- return stripTrailingSlash ( route ) . split ( '/' ) ;
208+ return route . split ( '/' ) . filter ( Boolean ) ;
226209 }
227210
228211 /**
@@ -232,74 +215,48 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
232215 * This function prioritizes exact segment matches first, followed by wildcard matches (`*`),
233216 * and finally deep wildcard matches (`**`) that consume all segments.
234217 *
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.
218+ * @param segments - The array of route path segments to match against the route tree.
219+ * @param node - The current node in the route tree to start traversal from. Defaults to the root node.
220+ * @param currentIndex - The index of the segment in `remainingSegments` currently being matched.
221+ * Defaults to `0` (the first segment).
237222 *
238223 * @returns The node that best matches the remaining segments or `undefined` if no match is found.
239224 */
240225 private traverseBySegments (
241- remainingSegments : string [ ] ,
226+ segments : string [ ] ,
242227 node = this . root ,
228+ currentIndex = 0 ,
243229 ) : 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 ( '**' ) ;
230+ if ( currentIndex >= segments . length ) {
231+ return node . metadata ? node : node . children . get ( '**' ) ;
249232 }
250233
251- // If the node has no children, end the traversal
252- if ( ! children . size ) {
253- return ;
234+ if ( ! node . children . size ) {
235+ return undefined ;
254236 }
255237
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- ) ;
238+ const segment = segments [ currentIndex ] ;
265239
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 ;
240+ // 1. Attempt exact match with the current segment.
241+ const exactMatch = node . children . get ( segment ) ;
242+ if ( exactMatch ) {
243+ const match = this . traverseBySegments ( segments , exactMatch , currentIndex + 1 ) ;
244+ if ( match ) {
245+ return match ;
246+ }
294247 }
295248
296- if ( ! currentBestMatchNode ) {
297- return candidateNode ;
249+ // 2. Attempt wildcard match ('*').
250+ const wildcardMatch = node . children . get ( '*' ) ;
251+ if ( wildcardMatch ) {
252+ const match = this . traverseBySegments ( segments , wildcardMatch , currentIndex + 1 ) ;
253+ if ( match ) {
254+ return match ;
255+ }
298256 }
299257
300- return candidateNode . insertionIndex < currentBestMatchNode . insertionIndex
301- ? candidateNode
302- : currentBestMatchNode ;
258+ // 3. Attempt double wildcard match ('**').
259+ return node . children . get ( '**' ) ;
303260 }
304261
305262 /**
@@ -310,7 +267,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
310267 */
311268 private createEmptyRouteTreeNode ( ) : RouteTreeNode < AdditionalMetadata > {
312269 return {
313- insertionIndex : - 1 ,
314270 children : new Map ( ) ,
315271 } ;
316272 }
0 commit comments