Revert "Revert "fix(router): Ensure canMatch guards run on wildcard routes (#53239)" (#53339)" (#53342)

This reverts commit ac8eb5b943.

PR Close #53342
This commit is contained in:
Andrew Scott 2023-12-03 19:03:57 -08:00 committed by Dylan Hunn
parent dc51f6ee3b
commit a9872ccbb9
4 changed files with 31 additions and 27 deletions

View file

@ -1106,9 +1106,6 @@
{
"name": "createUrlTreeFromSegmentGroup"
},
{
"name": "createWildcardMatchResult"
},
{
"name": "deactivateRouteAndItsChildren"
},

View file

@ -8,7 +8,7 @@
import {EnvironmentInjector, Type, ɵRuntimeError as RuntimeError} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {catchError, concatMap, defaultIfEmpty, first, last as rxjsLast, map, mergeMap, scan, switchMap, tap} from 'rxjs/operators';
import {catchError, concatMap, defaultIfEmpty, first, last, map, mergeMap, scan, switchMap, tap} from 'rxjs/operators';
import {AbsoluteRedirect, ApplyRedirects, canLoadFails, noMatch, NoMatch} from './apply_redirects';
import {createUrlTreeFromSnapshot} from './create_url_tree';
@ -19,9 +19,8 @@ import {RouterConfigLoader} from './router_config_loader';
import {ActivatedRouteSnapshot, getInherited, ParamsInheritanceStrategy, RouterStateSnapshot} from './router_state';
import {PRIMARY_OUTLET} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {last} from './utils/collection';
import {getOutlet, sortByMatchingOutlets} from './utils/config';
import {isImmediateMatch, match, MatchResult, matchWithChecks, noLeftoversInUrl, split} from './utils/config_matching';
import {isImmediateMatch, match, matchWithChecks, noLeftoversInUrl, split} from './utils/config_matching';
import {TreeNode} from './utils/tree';
import {isEmptyError} from './utils/type_guards';
@ -162,7 +161,7 @@ export class Recognizer {
return children;
}),
defaultIfEmpty(null as TreeNode<ActivatedRouteSnapshot>[] | null),
rxjsLast(),
last(),
mergeMap(children => {
if (children === null) return noMatch(segmentGroup);
// Because we may have matched two outlets to the same empty path segment, we can have
@ -235,8 +234,7 @@ export class Recognizer {
consumedSegments,
positionalParamSegments,
remainingSegments,
} = route.path === '**' ? createWildcardMatchResult(segments) :
match(segmentGroup, route, segments);
} = match(segmentGroup, route, segments);
if (!matched) return noMatch(segmentGroup);
// TODO(atscott): Move all of this under an if(ngDevMode) as a breaking change and allow stack
@ -268,17 +266,13 @@ export class Recognizer {
matchSegmentAgainstRoute(
injector: EnvironmentInjector, rawSegment: UrlSegmentGroup, route: Route,
segments: UrlSegment[], outlet: string): Observable<TreeNode<ActivatedRouteSnapshot>> {
let matchResult: Observable<MatchResult>;
const matchResult = matchWithChecks(rawSegment, route, segments, injector, this.urlSerializer);
if (route.path === '**') {
matchResult = of(createWildcardMatchResult(segments));
// Prior versions of the route matching algorithm would stop matching at the wildcard route.
// We should investigate a better strategy for any existing children. Otherwise, these
// child segments are silently dropped from the navigation.
// https://github.com/angular/angular/issues/40089
rawSegment.children = {};
} else {
matchResult = matchWithChecks(rawSegment, route, segments, injector, this.urlSerializer);
}
return matchResult.pipe(switchMap((result) => {
@ -437,13 +431,3 @@ function getData(route: Route): Data {
function getResolve(route: Route): ResolveData {
return route.resolve || {};
}
function createWildcardMatchResult(segments: UrlSegment[]): MatchResult {
return {
matched: true,
parameters: segments.length > 0 ? last(segments)!.parameters : {},
consumedSegments: segments,
remainingSegments: [],
positionalParamSegments: {},
};
}

View file

@ -15,6 +15,7 @@ import {runCanMatchGuards} from '../operators/check_guards';
import {defaultUrlMatcher, PRIMARY_OUTLET} from '../shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer} from '../url_tree';
import {last} from './collection';
import {getOrCreateRouteInjectorIfNeeded, getOutlet} from './config';
export interface MatchResult {
@ -52,6 +53,10 @@ export function matchWithChecks(
export function match(
segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): MatchResult {
if (route.path === '**') {
return createWildcardMatchResult(segments);
}
if (route.path === '') {
if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
return {...noMatch};
@ -88,6 +93,16 @@ export function match(
};
}
function createWildcardMatchResult(segments: UrlSegment[]): MatchResult {
return {
matched: true,
parameters: segments.length > 0 ? last(segments)!.parameters : {},
consumedSegments: segments,
remainingSegments: [],
positionalParamSegments: {},
};
}
export function split(
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
config: Route[]) {
@ -183,9 +198,6 @@ export function isImmediateMatch(
(outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))) {
return false;
}
if (route.path === '**') {
return true;
}
return match(rawSegment, route, segments).matched;
}

View file

@ -653,6 +653,17 @@ describe('recognize', async () => {
expect(s.root.fragment).toEqual('f1');
});
});
describe('guards', () => {
it('should run canMatch guards on wildcard routes', async () => {
const config = [
{path: '**', component: ComponentA, data: {id: 'a'}, canMatch: [() => false]},
{path: '**', component: ComponentB, data: {id: 'b'}}
];
const s = await recognize(config, 'a');
expect(s.root.firstChild!.data['id']).toEqual('b');
});
});
});
async function recognize(