Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions goldens/public-api/router/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export interface CanMatch {
canMatch(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
}

// @public
export type CanMatchFn = (route: Route, segments: UrlSegment[]) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean;

// @public
export class ChildActivationEnd {
constructor(
Expand Down
7 changes: 3 additions & 4 deletions packages/router/src/apply_redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {runCanLoadGuards} from './operators/check_guards';
import {RouterConfigLoader} from './router_config_loader';
import {navigationCancelingError, Params, PRIMARY_OUTLET} from './shared';
import {createRoot, squashSegmentGroup, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {forEach, wrapIntoObservable} from './utils/collection';
import {forEach} from './utils/collection';
import {getOrCreateRouteInjectorIfNeeded, getOutlet, sortByMatchingOutlets} from './utils/config';
import {isImmediateMatch, match, matchWithChecks, noLeftoversInUrl, split} from './utils/config_matching';

Expand Down Expand Up @@ -289,9 +289,8 @@ class ApplyRedirects {
switchMap(({matched, consumedSegments, remainingSegments}) => {
if (!matched) return noMatch(rawSegmentGroup);

// Only create the Route's `EnvironmentInjector` if it matches the attempted
// navigation
injector = getOrCreateRouteInjectorIfNeeded(route, injector);
// If the route has an injector created from providers, we should start using that.
injector = route._injector ?? injector;
const childConfig$ = this.getChildConfig(injector, route, segments);

return childConfig$.pipe(mergeMap((routerConfig: LoadedRouterConfig) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export {RouterLink, RouterLinkWithHref} from './directives/router_link';
export {RouterLinkActive} from './directives/router_link_active';
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, CanMatch, Data, LoadChildren, LoadChildrenCallback, QueryParamsHandling, Resolve, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, CanMatch, CanMatchFn, Data, LoadChildren, LoadChildrenCallback, QueryParamsHandling, Resolve, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
export {Navigation, NavigationBehaviorOptions, NavigationExtras, Router, UrlCreationOptions} from './router';
Expand Down
7 changes: 7 additions & 0 deletions packages/router/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,13 @@ export interface CanMatch {
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
}

/**
* The signature of a function used as a `CanMatch` guard on a `Route`.
*
* @publicApi
Comment thread
atscott marked this conversation as resolved.
Outdated
* @see `CanMatch`
* @see `Route`
*/
export type CanMatchFn = (route: Route, segments: UrlSegment[]) =>
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean;

Expand Down
7 changes: 3 additions & 4 deletions packages/router/src/recognize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class Recognizer {
let matchResult: Observable<{
snapshot: ActivatedRouteSnapshot,
consumedSegments: UrlSegment[],
remainingSegments: UrlSegment[]
remainingSegments: UrlSegment[],
}|null>;

if (route.path === '**') {
Expand Down Expand Up @@ -219,9 +219,8 @@ export class Recognizer {
return of(null);
}
const {snapshot, consumedSegments, remainingSegments} = result;
// Only create the Route's `EnvironmentInjector` if it matches the attempted
// navigation
injector = getOrCreateRouteInjectorIfNeeded(route, injector);
// If the route has an injector created from providers, we should start using that.
injector = route._injector ?? injector;
const childInjector = route._loadedInjector ?? injector;
const childConfig: Route[] = getChildConfig(route);

Expand Down
12 changes: 8 additions & 4 deletions packages/router/src/utils/config_matching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injector} from '@angular/core';
import {EnvironmentInjector, Injector} from '@angular/core';
import {Observable, of} from 'rxjs';
import {map} from 'rxjs/operators';

Expand All @@ -16,7 +16,7 @@ import {defaultUrlMatcher, PRIMARY_OUTLET} from '../shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer} from '../url_tree';

import {forEach} from './collection';
import {getOutlet} from './config';
import {getOrCreateRouteInjectorIfNeeded, getOutlet} from './config';

export interface MatchResult {
matched: boolean;
Expand All @@ -35,12 +35,16 @@ const noMatch: MatchResult = {
};

export function matchWithChecks(
segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[], injector: Injector,
urlSerializer: UrlSerializer): Observable<MatchResult> {
segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[],
injector: EnvironmentInjector, urlSerializer: UrlSerializer): Observable<MatchResult> {
const result = match(segmentGroup, route, segments);
if (!result.matched) {
return of(result);
}

// Only create the Route's `EnvironmentInjector` if it matches the attempted
// navigation
injector = getOrCreateRouteInjectorIfNeeded(route, injector);
return runCanMatchGuards(injector, route, segments, urlSerializer)
.pipe(
map((v) => v === true ? result : {...noMatch}),
Expand Down
37 changes: 36 additions & 1 deletion packages/router/test/apply_redirects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {EnvironmentInjector, NgModuleRef} from '@angular/core';
import {EnvironmentInjector, Injectable, NgModuleRef} from '@angular/core';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Observable, of} from 'rxjs';
import {delay, tap} from 'rxjs/operators';
Expand Down Expand Up @@ -229,6 +229,41 @@ describe('applyRedirects', () => {
});
});

it('should support CanMatch providers on the route', () => {
@Injectable({providedIn: 'root'})
class CanMatchGuard {
canMatch() {
return true;
}
}

const routes = [
{
path: 'a',
component: ComponentA,
canMatch: [CanMatchGuard],
providers: [CanMatchGuard],
},
{
path: 'a',
component: ComponentA,
providers: [],
}
];
applyRedirects(testModule.injector, null!, serializer, tree('a'), routes).subscribe({
next: () => {
// The 'a' segment matched, so we needed to create the injector for the `Route`
expect(getProvidersInjector(routes[0])).toBeDefined();
// The second `Route` did not match because the first did so we should not create an
// injector for it
expect(getProvidersInjector(routes[1])).not.toBeDefined();
},
error: () => {
throw 'Should not be reached';
}
});
});

describe('lazy loading', () => {
it('should load config on demand', () => {
const loadedConfig = {
Expand Down