6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
- import { addLeadingSlash , stripTrailingSlash } from '../utils/url' ;
9
+ import { addLeadingSlash } from '../utils/url' ;
10
10
import { RenderMode } from './route-config' ;
11
11
12
12
/**
@@ -78,13 +78,6 @@ export interface RouteTreeNodeMetadata {
78
78
* The `AdditionalMetadata` type parameter allows for extending the node metadata with custom data.
79
79
*/
80
80
interface 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
-
88
81
/**
89
82
* A map of child nodes, keyed by their corresponding route segment or wildcard.
90
83
*/
@@ -110,13 +103,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
110
103
*/
111
104
private readonly root = this . createEmptyRouteTreeNode ( ) ;
112
105
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
-
120
106
/**
121
107
* Inserts a new route into the route tree.
122
108
* 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> = {}>
134
120
// Replace parameterized segments (e.g., :id) with a wildcard (*) for matching
135
121
const normalizedSegment = segment [ 0 ] === ':' ? '*' : segment ;
136
122
let childNode = node . children . get ( normalizedSegment ) ;
137
-
138
123
if ( ! childNode ) {
139
124
childNode = this . createEmptyRouteTreeNode ( ) ;
140
125
node . children . set ( normalizedSegment , childNode ) ;
@@ -149,8 +134,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
149
134
...metadata ,
150
135
route : addLeadingSlash ( normalizedSegments . join ( '/' ) ) ,
151
136
} ;
152
-
153
- node . insertionIndex = this . insertionIndexCounter ++ ;
154
137
}
155
138
156
139
/**
@@ -222,7 +205,7 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
222
205
* @returns An array of path segments.
223
206
*/
224
207
private getPathSegments ( route : string ) : string [ ] {
225
- return stripTrailingSlash ( route ) . split ( '/' ) ;
208
+ return route . split ( '/' ) . filter ( Boolean ) ;
226
209
}
227
210
228
211
/**
@@ -232,74 +215,48 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
232
215
* This function prioritizes exact segment matches first, followed by wildcard matches (`*`),
233
216
* and finally deep wildcard matches (`**`) that consume all segments.
234
217
*
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).
237
222
*
238
223
* @returns The node that best matches the remaining segments or `undefined` if no match is found.
239
224
*/
240
225
private traverseBySegments (
241
- remainingSegments : string [ ] ,
226
+ segments : string [ ] ,
242
227
node = this . root ,
228
+ currentIndex = 0 ,
243
229
) : 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 ( '**' ) ;
249
232
}
250
233
251
- // If the node has no children, end the traversal
252
- if ( ! children . size ) {
253
- return ;
234
+ if ( ! node . children . size ) {
235
+ return undefined ;
254
236
}
255
237
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 ] ;
265
239
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
+ }
294
247
}
295
248
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
+ }
298
256
}
299
257
300
- return candidateNode . insertionIndex < currentBestMatchNode . insertionIndex
301
- ? candidateNode
302
- : currentBestMatchNode ;
258
+ // 3. Attempt double wildcard match ('**').
259
+ return node . children . get ( '**' ) ;
303
260
}
304
261
305
262
/**
@@ -310,7 +267,6 @@ export class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}>
310
267
*/
311
268
private createEmptyRouteTreeNode ( ) : RouteTreeNode < AdditionalMetadata > {
312
269
return {
313
- insertionIndex : - 1 ,
314
270
children : new Map ( ) ,
315
271
} ;
316
272
}
0 commit comments