Skip to content

Commit c1c7384

Browse files
atscottpkozlowski-opensource
authored andcommitted
feat(router): Add reusable types for router guards (#54580)
This refactor makes it easier to update the return types of guards. Rather than having to track what types guards can return, which may change with new features over time, `MaybeAsync<GuardResult>` can be used instead. PR Close #54580
1 parent fb540e1 commit c1c7384

File tree

5 files changed

+63
-65
lines changed

5 files changed

+63
-65
lines changed

goldens/public-api/router/index.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,47 +115,47 @@ export abstract class BaseRouteReuseStrategy implements RouteReuseStrategy {
115115
// @public @deprecated
116116
export interface CanActivate {
117117
// (undocumented)
118-
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
118+
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
119119
}
120120

121121
// @public @deprecated
122122
export interface CanActivateChild {
123123
// (undocumented)
124-
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
124+
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
125125
}
126126

127127
// @public
128-
export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
128+
export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<GuardResult>;
129129

130130
// @public
131-
export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
131+
export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<GuardResult>;
132132

133133
// @public @deprecated
134134
export interface CanDeactivate<T> {
135135
// (undocumented)
136-
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
136+
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot): MaybeAsync<GuardResult>;
137137
}
138138

139139
// @public
140-
export type CanDeactivateFn<T> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
140+
export type CanDeactivateFn<T> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => MaybeAsync<GuardResult>;
141141

142142
// @public @deprecated
143143
export interface CanLoad {
144144
// (undocumented)
145-
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
145+
canLoad(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
146146
}
147147

148148
// @public @deprecated
149-
export type CanLoadFn = (route: Route, segments: UrlSegment[]) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
149+
export type CanLoadFn = (route: Route, segments: UrlSegment[]) => MaybeAsync<GuardResult>;
150150

151151
// @public @deprecated
152152
export interface CanMatch {
153153
// (undocumented)
154-
canMatch(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
154+
canMatch(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
155155
}
156156

157157
// @public
158-
export type CanMatchFn = (route: Route, segments: UrlSegment[]) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
158+
export type CanMatchFn = (route: Route, segments: UrlSegment[]) => MaybeAsync<GuardResult>;
159159

160160
// @public
161161
export class ChildActivationEnd {
@@ -305,6 +305,9 @@ export interface ExtraOptions extends InMemoryScrollingOptions, RouterConfigOpti
305305
useHash?: boolean;
306306
}
307307

308+
// @public
309+
export type GuardResult = boolean | UrlTree;
310+
308311
// @public
309312
export class GuardsCheckEnd extends RouterEvent {
310313
constructor(
@@ -396,6 +399,9 @@ export function mapToResolve<T>(provider: Type<{
396399
resolve: ResolveFn<T>;
397400
}>): ResolveFn<T>;
398401

402+
// @public
403+
export type MaybeAsync<T> = T | Observable<T> | Promise<T>;
404+
399405
// @public
400406
export interface Navigation {
401407
extractedUrl: UrlTree;
@@ -592,7 +598,7 @@ export type QueryParamsHandling = 'merge' | 'preserve' | '';
592598
// @public @deprecated
593599
export interface Resolve<T> {
594600
// (undocumented)
595-
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T;
601+
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<T>;
596602
}
597603

598604
// @public
@@ -618,7 +624,7 @@ export class ResolveEnd extends RouterEvent {
618624
}
619625

620626
// @public
621-
export type ResolveFn<T> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<T> | Promise<T> | T;
627+
export type ResolveFn<T> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<T>;
622628

623629
// @public
624630
export class ResolveStart extends RouterEvent {

packages/router/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export {
3636
} from './events';
3737
export {
3838
CanActivateChildFn,
39+
MaybeAsync,
40+
GuardResult,
3941
CanActivateFn,
4042
CanDeactivateFn,
4143
CanLoadFn,

packages/router/src/models.ts

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ export type OnSameUrlNavigation = 'reload' | 'ignore';
6464
*/
6565
export type DeprecatedGuard = ProviderToken<any> | any;
6666

67+
/**
68+
* The supported types that can be returned from a `Router` guard.
69+
*
70+
* @see [Routing tutorial](guide/router-tutorial-toh#milestone-5-route-guards)
71+
* @publicApi
72+
*/
73+
export type GuardResult = boolean | UrlTree;
74+
75+
/**
76+
* Type used to represent a value which may be synchronous or async.
77+
*
78+
* @publicApi
79+
*/
80+
export type MaybeAsync<T> = T | Observable<T> | Promise<T>;
81+
6782
/**
6883
* Represents a route configuration for the Router service.
6984
* An array of `Route` objects, used in `Router.config` and for nested route configurations
@@ -693,7 +708,7 @@ export interface LoadedRouterConfig {
693708
* canActivate(
694709
* route: ActivatedRouteSnapshot,
695710
* state: RouterStateSnapshot
696-
* ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree {
711+
* ): MaybeAsync<GuardResult> {
697712
* return this.permissions.canActivate(this.currentUser, route.params.id);
698713
* }
699714
* }
@@ -725,10 +740,7 @@ export interface LoadedRouterConfig {
725740
* @see {@link CanActivateFn}
726741
*/
727742
export interface CanActivate {
728-
canActivate(
729-
route: ActivatedRouteSnapshot,
730-
state: RouterStateSnapshot,
731-
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
743+
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
732744
}
733745

734746
/**
@@ -754,7 +766,7 @@ export interface CanActivate {
754766
export type CanActivateFn = (
755767
route: ActivatedRouteSnapshot,
756768
state: RouterStateSnapshot,
757-
) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
769+
) => MaybeAsync<GuardResult>;
758770

759771
/**
760772
* @description
@@ -782,7 +794,7 @@ export type CanActivateFn = (
782794
* canActivateChild(
783795
* route: ActivatedRouteSnapshot,
784796
* state: RouterStateSnapshot
785-
* ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree {
797+
* ): MaybeAsync<GuardResult> {
786798
* return this.permissions.canActivate(this.currentUser, route.params.id);
787799
* }
788800
* }
@@ -822,7 +834,7 @@ export interface CanActivateChild {
822834
canActivateChild(
823835
childRoute: ActivatedRouteSnapshot,
824836
state: RouterStateSnapshot,
825-
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
837+
): MaybeAsync<GuardResult>;
826838
}
827839

828840
/**
@@ -843,7 +855,7 @@ export interface CanActivateChild {
843855
export type CanActivateChildFn = (
844856
childRoute: ActivatedRouteSnapshot,
845857
state: RouterStateSnapshot,
846-
) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
858+
) => MaybeAsync<GuardResult>;
847859

848860
/**
849861
* @description
@@ -879,7 +891,7 @@ export type CanActivateChildFn = (
879891
* currentRoute: ActivatedRouteSnapshot,
880892
* currentState: RouterStateSnapshot,
881893
* nextState: RouterStateSnapshot
882-
* ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree {
894+
* ): MaybeAsync<GuardResult> {
883895
* return this.permissions.canDeactivate(this.currentUser, route.params.id);
884896
* }
885897
* }
@@ -911,7 +923,7 @@ export interface CanDeactivate<T> {
911923
currentRoute: ActivatedRouteSnapshot,
912924
currentState: RouterStateSnapshot,
913925
nextState: RouterStateSnapshot,
914-
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
926+
): MaybeAsync<GuardResult>;
915927
}
916928

917929
/**
@@ -934,7 +946,7 @@ export type CanDeactivateFn<T> = (
934946
currentRoute: ActivatedRouteSnapshot,
935947
currentState: RouterStateSnapshot,
936948
nextState: RouterStateSnapshot,
937-
) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
949+
) => MaybeAsync<GuardResult>;
938950

939951
/**
940952
* @description
@@ -1002,10 +1014,7 @@ export type CanDeactivateFn<T> = (
10021014
* @see {@link CanMatchFn}
10031015
*/
10041016
export interface CanMatch {
1005-
canMatch(
1006-
route: Route,
1007-
segments: UrlSegment[],
1008-
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
1017+
canMatch(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
10091018
}
10101019

10111020
/**
@@ -1023,10 +1032,7 @@ export interface CanMatch {
10231032
* @publicApi
10241033
* @see {@link Route}
10251034
*/
1026-
export type CanMatchFn = (
1027-
route: Route,
1028-
segments: UrlSegment[],
1029-
) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
1035+
export type CanMatchFn = (route: Route, segments: UrlSegment[]) => MaybeAsync<GuardResult>;
10301036

10311037
/**
10321038
* @description
@@ -1126,10 +1132,7 @@ export type CanMatchFn = (
11261132
* @see {@link ResolveFn}
11271133
*/
11281134
export interface Resolve<T> {
1129-
resolve(
1130-
route: ActivatedRouteSnapshot,
1131-
state: RouterStateSnapshot,
1132-
): Observable<T> | Promise<T> | T;
1135+
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<T>;
11331136
}
11341137

11351138
/**
@@ -1176,7 +1179,7 @@ export interface Resolve<T> {
11761179
export type ResolveFn<T> = (
11771180
route: ActivatedRouteSnapshot,
11781181
state: RouterStateSnapshot,
1179-
) => Observable<T> | Promise<T> | T;
1182+
) => MaybeAsync<T>;
11801183

11811184
/**
11821185
* @description
@@ -1233,10 +1236,7 @@ export type ResolveFn<T> = (
12331236
* @deprecated Use {@link CanMatchFn} instead
12341237
*/
12351238
export interface CanLoad {
1236-
canLoad(
1237-
route: Route,
1238-
segments: UrlSegment[],
1239-
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
1239+
canLoad(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult>;
12401240
}
12411241

12421242
/**
@@ -1248,10 +1248,7 @@ export interface CanLoad {
12481248
* @see {@link CanMatchFn}
12491249
* @deprecated Use `Route.canMatch` and `CanMatchFn` instead
12501250
*/
1251-
export type CanLoadFn = (
1252-
route: Route,
1253-
segments: UrlSegment[],
1254-
) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
1251+
export type CanLoadFn = (route: Route, segments: UrlSegment[]) => MaybeAsync<GuardResult>;
12551252

12561253
/**
12571254
* @description

packages/router/src/operators/check_guards.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
CanActivateChildFn,
2525
CanActivateFn,
2626
CanDeactivateFn,
27+
GuardResult,
2728
CanLoadFn,
2829
CanMatchFn,
2930
Route,
@@ -91,12 +92,9 @@ function runCanDeactivateChecks(
9192
mergeMap((check) =>
9293
runCanDeactivate(check.component, check.route, currRSS, futureRSS, injector),
9394
),
94-
first(
95-
(result) => {
96-
return result !== true;
97-
},
98-
true as boolean | UrlTree,
99-
),
95+
first((result) => {
96+
return result !== true;
97+
}, true),
10098
);
10199
}
102100

@@ -115,12 +113,9 @@ function runCanActivateChecks(
115113
runCanActivate(futureSnapshot, check.route, injector),
116114
);
117115
}),
118-
first(
119-
(result) => {
120-
return result !== true;
121-
},
122-
true as boolean | UrlTree,
123-
),
116+
first((result) => {
117+
return result !== true;
118+
}, true),
124119
);
125120
}
126121

@@ -164,7 +159,7 @@ function runCanActivate(
164159
futureRSS: RouterStateSnapshot,
165160
futureARS: ActivatedRouteSnapshot,
166161
injector: EnvironmentInjector,
167-
): Observable<boolean | UrlTree> {
162+
): Observable<GuardResult> {
168163
const canActivate = futureARS.routeConfig ? futureARS.routeConfig.canActivate : null;
169164
if (!canActivate || canActivate.length === 0) return of(true);
170165

@@ -189,7 +184,7 @@ function runCanActivateChild(
189184
futureRSS: RouterStateSnapshot,
190185
path: ActivatedRouteSnapshot[],
191186
injector: EnvironmentInjector,
192-
): Observable<boolean | UrlTree> {
187+
): Observable<GuardResult> {
193188
const futureARS = path[path.length - 1];
194189

195190
const canActivateChildGuards = path
@@ -227,7 +222,7 @@ function runCanDeactivate(
227222
currRSS: RouterStateSnapshot,
228223
futureRSS: RouterStateSnapshot,
229224
injector: EnvironmentInjector,
230-
): Observable<boolean | UrlTree> {
225+
): Observable<GuardResult> {
231226
const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null;
232227
if (!canDeactivate || canDeactivate.length === 0) return of(true);
233228
const canDeactivateObservables = canDeactivate.map((c: any) => {

packages/router/src/operators/prioritized_guard_value.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
import {combineLatest, Observable, OperatorFunction} from 'rxjs';
1010
import {filter, map, startWith, switchMap, take} from 'rxjs/operators';
1111

12+
import {GuardResult} from '../models';
1213
import {UrlTree} from '../url_tree';
1314

1415
const INITIAL_VALUE = /* @__PURE__ */ Symbol('INITIAL_VALUE');
1516
declare type INTERIM_VALUES = typeof INITIAL_VALUE | boolean | UrlTree;
1617

17-
export function prioritizedGuardValue(): OperatorFunction<
18-
Observable<boolean | UrlTree>[],
19-
boolean | UrlTree
20-
> {
18+
export function prioritizedGuardValue(): OperatorFunction<Observable<GuardResult>[], GuardResult> {
2119
return switchMap((obs) => {
2220
return combineLatest(
2321
obs.map((o) => o.pipe(take(1), startWith(INITIAL_VALUE as INTERIM_VALUES))),
@@ -40,7 +38,7 @@ export function prioritizedGuardValue(): OperatorFunction<
4038
// Everything resolved to true. Return true.
4139
return true;
4240
}),
43-
filter((item): item is boolean | UrlTree => item !== INITIAL_VALUE),
41+
filter((item): item is GuardResult => item !== INITIAL_VALUE),
4442
take(1),
4543
);
4644
});

0 commit comments

Comments
 (0)