Skip to content

Commit 8f63084

Browse files
atscottalxhub
authored andcommitted
fix(router): Do not unnecessarily run matcher twice on route matching (angular#57530)
This commit makes a small update to the route matching algorithm to avoid running the matcher function of a route twice. fixes angular#57511 PR Close angular#57530
1 parent f454ad3 commit 8f63084

File tree

3 files changed

+41
-35
lines changed

3 files changed

+41
-35
lines changed

packages/router/src/recognize.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {PRIMARY_OUTLET} from './shared';
3737
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
3838
import {getOutlet, sortByMatchingOutlets} from './utils/config';
3939
import {
40-
isImmediateMatch,
40+
emptyPathMatch,
4141
match,
4242
matchWithChecks,
4343
noLeftoversInUrl,
@@ -293,7 +293,23 @@ export class Recognizer {
293293
allowRedirects: boolean,
294294
parentRoute: ActivatedRouteSnapshot,
295295
): Observable<TreeNode<ActivatedRouteSnapshot> | NoLeftoversInUrl> {
296-
if (!isImmediateMatch(route, rawSegment, segments, outlet)) return noMatch(rawSegment);
296+
// We allow matches to empty paths when the outlets differ so we can match a url like `/(b:b)` to
297+
// a config like
298+
// * `{path: '', children: [{path: 'b', outlet: 'b'}]}`
299+
// or even
300+
// * `{path: '', outlet: 'a', children: [{path: 'b', outlet: 'b'}]`
301+
//
302+
// The exception here is when the segment outlet is for the primary outlet. This would
303+
// result in a match inside the named outlet because all children there are written as primary
304+
// outlets. So we need to prevent child named outlet matches in a url like `/b` in a config like
305+
// * `{path: '', outlet: 'x' children: [{path: 'b'}]}`
306+
// This should only match if the url is `/(x:b)`.
307+
if (
308+
getOutlet(route) !== outlet &&
309+
(outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))
310+
) {
311+
return noMatch(rawSegment);
312+
}
297313

298314
if (route.redirectTo === undefined) {
299315
return this.matchSegmentAgainstRoute(

packages/router/src/utils/config_matching.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ function containsEmptyPathMatches(
195195
return routes.some((r) => emptyPathMatch(segmentGroup, slicedSegments, r));
196196
}
197197

198-
function emptyPathMatch(
198+
export function emptyPathMatch(
199199
segmentGroup: UrlSegmentGroup,
200200
slicedSegments: UrlSegment[],
201201
r: Route,
@@ -207,37 +207,6 @@ function emptyPathMatch(
207207
return r.path === '';
208208
}
209209

210-
/**
211-
* Determines if `route` is a path match for the `rawSegment`, `segments`, and `outlet` without
212-
* verifying that its children are a full match for the remainder of the `rawSegment` children as
213-
* well.
214-
*/
215-
export function isImmediateMatch(
216-
route: Route,
217-
rawSegment: UrlSegmentGroup,
218-
segments: UrlSegment[],
219-
outlet: string,
220-
): boolean {
221-
// We allow matches to empty paths when the outlets differ so we can match a url like `/(b:b)` to
222-
// a config like
223-
// * `{path: '', children: [{path: 'b', outlet: 'b'}]}`
224-
// or even
225-
// * `{path: '', outlet: 'a', children: [{path: 'b', outlet: 'b'}]`
226-
//
227-
// The exception here is when the segment outlet is for the primary outlet. This would
228-
// result in a match inside the named outlet because all children there are written as primary
229-
// outlets. So we need to prevent child named outlet matches in a url like `/b` in a config like
230-
// * `{path: '', outlet: 'x' children: [{path: 'b'}]}`
231-
// This should only match if the url is `/(x:b)`.
232-
if (
233-
getOutlet(route) !== outlet &&
234-
(outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))
235-
) {
236-
return false;
237-
}
238-
return match(rawSegment, route, segments).matched;
239-
}
240-
241210
export function noLeftoversInUrl(
242211
segmentGroup: UrlSegmentGroup,
243212
segments: UrlSegment[],

packages/router/test/recognize.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {EnvironmentInjector} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
1111

12-
import {Routes} from '../src/models';
12+
import {Routes, UrlMatcher} from '../src/models';
1313
import {Recognizer} from '../src/recognize';
1414
import {RouterConfigLoader} from '../src/router_config_loader';
1515
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../src/router_state';
@@ -700,6 +700,27 @@ describe('recognize', async () => {
700700
});
701701

702702
describe('custom path matchers', async () => {
703+
it('should run once', async () => {
704+
let calls = 0;
705+
const matcher: UrlMatcher = (s) => {
706+
calls++;
707+
return {consumed: s};
708+
};
709+
710+
const s = await recognize(
711+
[
712+
{
713+
matcher,
714+
component: ComponentA,
715+
},
716+
],
717+
'/a/1/b',
718+
);
719+
const a = s.root.firstChild!;
720+
checkActivatedRoute(a, 'a/1/b', {}, ComponentA);
721+
expect(calls).toBe(1);
722+
});
723+
703724
it('should use custom path matcher', async () => {
704725
const matcher = (s: any, g: any, r: any) => {
705726
if (s[0].path === 'a') {

0 commit comments

Comments
 (0)