Skip to content

Commit 926c35f

Browse files
atscottAndrewKushnir
authored andcommitted
docs: Deprecate class and InjectionToken and resolvers (#47924)
Class and `InjectionToken`-based guards and resolvers are not as configurable, are less re-usable, require more boilerplate, cannot be defined inline with the route, and require more in-depth knowledge of Angular features (`Injectable`/providers). In short, they're less powerful and more cumbersome. In addition, continued support increases the API surface which in turn increases bundle size, code complexity, the learning curve and API surface to teach, maintenance cost, and cognitive load (needing to grok several different types of information in a single place). Lastly, supporting only the `CanXFn` types for guards and `ResolveFn` type for resolvers in the `Route` interface will enable better code completion and integration with TypeScript. For example, when writing an inline functional resolver today, the function is typed as `any` and does not provide completions for the `ResolveFn` parameters. By restricting the type to only `ResolveFn`, in the example below TypeScript would be able to correctly identify the `route` parameter as `ActivatedRouteSnapshot` and when authoring the inline route, the language service would be able to autocomplete the function parameters. ``` const userRoute: Route = { path: 'user/:id', resolve: { "user": (route) => inject(UserService).getUser(route.params['id']); } }; ``` Importantly, this deprecation only affects the support for class and `InjectionToken` guards at the `Route` definition. `Injectable` classes and `InjectionToken` providers are _not_ being deprecated in the general sense. Functional guards are robust enough to even support the existing class-based guards through a transform: ``` function mapToCanMatch(providers: Array<Type<{canMatch: CanMatchFn}>>): CanMatchFn[] { return providers.map(provider => (...params) => inject(provider).canMatch(...params)); } const route = { path: 'admin', canMatch: mapToCanMatch([AdminGuard]), }; ``` With regards to tests, because of the ability to map `Injectable` classes to guard functions as outlined above, nothing _needs_ to change if projects prefer testing guards the way they do today. Functional guards can also be written in a way that's either testable with `runInContext` or by passing mock implementations of dependencies. For example: ``` export function myGuardWithMockableDeps( dep1 = inject(MyService), dep2 = inject(MyService2), dep3 = inject(MyService3), ) { } const route = { path: 'admin', canActivate: [() => myGuardWithMockableDeps()] } // test file const guardResultWithMockDeps = myGuardWithMockableDeps(mockService1, mockService2, mockService3); const guardResultWithRealDeps = TestBed.inject(EnvironmentInjector).runInContext(myGuardWithMockableDeps); ``` DEPRECATED: Class and `InjectionToken` guards and resolvers are deprecated. Instead, write guards as plain JavaScript functions and inject dependencies with `inject` from `@angular/core`. PR Close #47924
1 parent 7f68a70 commit 926c35f

File tree

5 files changed

+61
-26
lines changed

5 files changed

+61
-26
lines changed

aio/content/guide/deprecations.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ v15 - v18
117117
| `@angular/router` | [`RouterLinkWithHref` directive](#router) | v15 | v17 |
118118
| `@angular/router` | [Router writeable properties](#router-writable-properties) | v15.1 | v17 |
119119
| `@angular/router` | [Router CanLoad guards](#router-can-load) | v15.1 | v17 |
120+
| `@angular/router` | [class and `InjectionToken` guards and resolvers](#router) | v15.2 | v17 |
120121

121122
### Deprecated features with no planned removal version
122123

@@ -203,6 +204,8 @@ In the [API reference section](api) of this site, deprecated APIs are indicated
203204
| [`RouterLinkWithHref` directive](api/router/RouterLinkWithHref) | Use `RouterLink` instead. | v15 | The `RouterLinkWithHref` directive code was merged into `RouterLink`. Now the `RouterLink` directive can be used for all elements that have `routerLink` attribute. |
204205
| [`provideRoutes` function](api/router/provideRoutes) | Use `ROUTES` `InjectionToken` instead. | v15 | The `provideRoutes` helper function is minimally useful and can be unintentionally used instead of `provideRouter` due to similar spelling. |
205206
| [`setupTestingRouter` function](api/router/testing/setupTestingRouter) | Use `provideRouter` or `RouterTestingModule` instead. | v15.1 | The `setupTestingRouter` function is not necessary. The `Router` is initialized based on the DI configuration in tests as it would be in production. |
207+
| [class and `InjectionToken` guards and resolvers](api/router/DeprecatedGuard) | Use plain JavaScript functions instead. | v15.2 | Functional guards are simpler and more powerful than class and token-based guards. |
208+
206209

207210
<a id="platform-browser"></a>
208211

goldens/public-api/router/index.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { OnChanges } from '@angular/core';
2424
import { OnDestroy } from '@angular/core';
2525
import { OnInit } from '@angular/core';
2626
import { Provider } from '@angular/core';
27+
import { ProviderToken } from '@angular/core';
2728
import { QueryList } from '@angular/core';
2829
import { Renderer2 } from '@angular/core';
2930
import { SimpleChanges } from '@angular/core';
@@ -111,13 +112,13 @@ export abstract class BaseRouteReuseStrategy implements RouteReuseStrategy {
111112
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void;
112113
}
113114

114-
// @public
115+
// @public @deprecated
115116
export interface CanActivate {
116117
// (undocumented)
117118
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
118119
}
119120

120-
// @public
121+
// @public @deprecated
121122
export interface CanActivateChild {
122123
// (undocumented)
123124
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
@@ -129,7 +130,7 @@ export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: Rou
129130
// @public
130131
export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
131132

132-
// @public
133+
// @public @deprecated
133134
export interface CanDeactivate<T> {
134135
// (undocumented)
135136
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
@@ -147,7 +148,7 @@ export interface CanLoad {
147148
// @public @deprecated
148149
export type CanLoadFn = (route: Route, segments: UrlSegment[]) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
149150

150-
// @public
151+
// @public @deprecated
151152
export interface CanMatch {
152153
// (undocumented)
153154
canMatch(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
@@ -237,6 +238,9 @@ export class DefaultUrlSerializer implements UrlSerializer {
237238
serialize(tree: UrlTree): string;
238239
}
239240

241+
// @public @deprecated
242+
export type DeprecatedGuard = ProviderToken<any> | any;
243+
240244
// @public
241245
export type DetachedRouteHandle = {};
242246

@@ -561,15 +565,15 @@ export function provideRoutes(routes: Routes): Provider[];
561565
// @public
562566
export type QueryParamsHandling = 'merge' | 'preserve' | '';
563567

564-
// @public
568+
// @public @deprecated
565569
export interface Resolve<T> {
566570
// (undocumented)
567571
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T;
568572
}
569573

570574
// @public
571575
export type ResolveData = {
572-
[key: string | symbol]: any | ResolveFn<unknown>;
576+
[key: string | symbol]: ResolveFn<unknown> | DeprecatedGuard;
573577
};
574578

575579
// @public
@@ -611,12 +615,12 @@ export class ResolveStart extends RouterEvent {
611615

612616
// @public
613617
export interface Route {
614-
canActivate?: Array<CanActivateFn | any>;
615-
canActivateChild?: Array<CanActivateChildFn | any>;
616-
canDeactivate?: Array<CanDeactivateFn<any> | any>;
618+
canActivate?: Array<CanActivateFn | DeprecatedGuard>;
619+
canActivateChild?: Array<CanActivateChildFn | DeprecatedGuard>;
620+
canDeactivate?: Array<CanDeactivateFn<any> | DeprecatedGuard>;
617621
// @deprecated
618-
canLoad?: Array<CanLoadFn | any>;
619-
canMatch?: Array<Type<CanMatch> | InjectionToken<CanMatchFn> | CanMatchFn>;
622+
canLoad?: Array<CanLoadFn | DeprecatedGuard>;
623+
canMatch?: Array<CanMatchFn | DeprecatedGuard>;
620624
children?: Routes;
621625
component?: Type<any>;
622626
data?: Data;

packages/router/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export {RouterLink, RouterLinkWithHref} from './directives/router_link';
1212
export {RouterLinkActive} from './directives/router_link_active';
1313
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
1414
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode as NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationSkippedCode, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
15-
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, DefaultExport, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, OnSameUrlNavigation, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
15+
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, DefaultExport, DeprecatedGuard, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, OnSameUrlNavigation, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
1616
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
1717
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
1818
export {DebugTracingFeature, DisabledInitialNavigationFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, NavigationErrorHandlerFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, RouterHashLocationFeature, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withNavigationErrorHandler, withPreloading, withRouterConfig} from './provide_router';

packages/router/src/models.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {EnvironmentInjector, EnvironmentProviders, InjectionToken, NgModuleFactory, Provider, Type} from '@angular/core';
9+
import {EnvironmentInjector, EnvironmentProviders, NgModuleFactory, Provider, ProviderToken, Type} from '@angular/core';
1010
import {Observable} from 'rxjs';
1111

1212
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
@@ -38,6 +38,23 @@ import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
3838
*/
3939
export type OnSameUrlNavigation = 'reload'|'ignore';
4040

41+
/**
42+
* The `InjectionToken` and `@Injectable` classes for guards and resolvers are deprecated in favor
43+
* of plain JavaScript functions instead.. Dependency injection can still be achieved using the
44+
* `inject` function from `@angular/core`.
45+
*
46+
* @deprecated
47+
* @see CanMatchFn
48+
* @see CanLoadFn
49+
* @see CanActivateFn
50+
* @see CanActivateChildFn
51+
* @see CanDeactivateFn
52+
* @see ResolveFn
53+
* @see inject
54+
* @publicApi
55+
*/
56+
export type DeprecatedGuard = ProviderToken<any>|any;
57+
4158
/**
4259
* Represents a route configuration for the Router service.
4360
* An array of `Route` objects, used in `Router.config` and for nested route configurations
@@ -110,7 +127,7 @@ export type Data = {
110127
* @publicApi
111128
*/
112129
export type ResolveData = {
113-
[key: string|symbol]: any|ResolveFn<unknown>
130+
[key: string|symbol]: ResolveFn<unknown>|DeprecatedGuard
114131
};
115132

116133
/**
@@ -517,7 +534,7 @@ export interface Route {
517534
* When using a function rather than DI tokens, the function can call `inject` to get any required
518535
* dependencies. This `inject` call must be done in a synchronous context.
519536
*/
520-
canActivate?: Array<CanActivateFn|any>;
537+
canActivate?: Array<CanActivateFn|DeprecatedGuard>;
521538
/**
522539
* An array of `CanMatchFn` or DI tokens used to look up `CanMatch()`
523540
* handlers, in order to determine if the current user is allowed to
@@ -526,7 +543,7 @@ export interface Route {
526543
* When using a function rather than DI tokens, the function can call `inject` to get any required
527544
* dependencies. This `inject` call must be done in a synchronous context.
528545
*/
529-
canMatch?: Array<Type<CanMatch>|InjectionToken<CanMatchFn>|CanMatchFn>;
546+
canMatch?: Array<CanMatchFn|DeprecatedGuard>;
530547
/**
531548
* An array of `CanActivateChildFn` or DI tokens used to look up `CanActivateChild()` handlers,
532549
* in order to determine if the current user is allowed to activate
@@ -535,7 +552,7 @@ export interface Route {
535552
* When using a function rather than DI tokens, the function can call `inject` to get any required
536553
* dependencies. This `inject` call must be done in a synchronous context.
537554
*/
538-
canActivateChild?: Array<CanActivateChildFn|any>;
555+
canActivateChild?: Array<CanActivateChildFn|DeprecatedGuard>;
539556
/**
540557
* An array of `CanDeactivateFn` or DI tokens used to look up `CanDeactivate()`
541558
* handlers, in order to determine if the current user is allowed to
@@ -544,7 +561,7 @@ export interface Route {
544561
* When using a function rather than DI tokens, the function can call `inject` to get any required
545562
* dependencies. This `inject` call must be done in a synchronous context.
546563
*/
547-
canDeactivate?: Array<CanDeactivateFn<any>|any>;
564+
canDeactivate?: Array<CanDeactivateFn<any>|DeprecatedGuard>;
548565
/**
549566
* An array of `CanLoadFn` or DI tokens used to look up `CanLoad()`
550567
* handlers, in order to determine if the current user is allowed to
@@ -554,7 +571,7 @@ export interface Route {
554571
* dependencies. This `inject` call must be done in a synchronous context.
555572
* @deprecated Use `canMatch` instead
556573
*/
557-
canLoad?: Array<CanLoadFn|any>;
574+
canLoad?: Array<CanLoadFn|DeprecatedGuard>;
558575
/**
559576
* Additional developer-defined data provided to the component via
560577
* `ActivatedRoute`. By default, no additional data is passed.
@@ -696,6 +713,8 @@ export interface LoadedRouterConfig {
696713
* ```
697714
*
698715
* @publicApi
716+
* @deprecated Use plain JavaScript functions instead.
717+
* @see CanActivateFn
699718
*/
700719
export interface CanActivate {
701720
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
@@ -791,6 +810,8 @@ export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSn
791810
* ```
792811
*
793812
* @publicApi
813+
* @deprecated Use plain JavaScript functions instead.
814+
* @see CanActivateChildFn
794815
*/
795816
export interface CanActivateChild {
796817
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot):
@@ -880,6 +901,8 @@ export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: Rou
880901
* ```
881902
*
882903
* @publicApi
904+
* @deprecated Use plain JavaScript functions instead.
905+
* @see CanDeactivateFn
883906
*/
884907
export interface CanDeactivate<T> {
885908
canDeactivate(
@@ -982,6 +1005,8 @@ export type CanDeactivateFn<T> =
9821005
* ```
9831006
*
9841007
* @publicApi
1008+
* @deprecated Use plain JavaScript functions instead.
1009+
* @see CanMatchFn
9851010
*/
9861011
export interface CanMatch {
9871012
canMatch(route: Route, segments: UrlSegment[]):
@@ -1112,6 +1137,8 @@ export type CanMatchFn = (route: Route, segments: UrlSegment[]) =>
11121137
* The order of execution is: BaseGuard, ChildGuard, BaseDataResolver, ChildDataResolver.
11131138
*
11141139
* @publicApi
1140+
* @deprecated Use plain JavaScript functions instead.
1141+
* @see ResolveFn
11151142
*/
11161143
export interface Resolve<T> {
11171144
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T>|Promise<T>|T;
@@ -1196,7 +1223,7 @@ export type ResolveFn<T> = (route: ActivatedRouteSnapshot, state: RouterStateSna
11961223
* ```
11971224
*
11981225
* @publicApi
1199-
* @deprecated Use `CanMatch` instead
1226+
* @deprecated Use `CanMatchFn` instead
12001227
*/
12011228
export interface CanLoad {
12021229
canLoad(route: Route, segments: UrlSegment[]):

packages/router/src/operators/check_guards.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {concat, defer, from, MonoTypeOperatorFunction, Observable, of, OperatorF
1111
import {concatMap, first, map, mergeMap, tap} from 'rxjs/operators';
1212

1313
import {ActivationStart, ChildActivationStart, Event} from '../events';
14-
import {CanActivateChild, CanActivateChildFn, CanActivateFn, Route} from '../models';
14+
import {CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivateFn, CanLoadFn, CanMatchFn, Route} from '../models';
1515
import {redirectingNavigationError} from '../navigation_canceling_error';
1616
import {NavigationTransition} from '../navigation_transition';
1717
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../router_state';
@@ -142,7 +142,8 @@ function runCanActivateChild(
142142
getTokenOrFunctionIdentity<CanActivateChild>(canActivateChild, closestInjector);
143143
const guardVal = isCanActivateChild(guard) ?
144144
guard.canActivateChild(futureARS, futureRSS) :
145-
closestInjector.runInContext(() => guard(futureARS, futureRSS));
145+
closestInjector.runInContext(
146+
() => (guard as CanActivateChildFn)(futureARS, futureRSS));
146147
return wrapIntoObservable(guardVal).pipe(first());
147148
});
148149
return of(guardsMapped).pipe(prioritizedGuardValue());
@@ -161,8 +162,8 @@ function runCanDeactivate(
161162
const guard = getTokenOrFunctionIdentity<any>(c, closestInjector);
162163
const guardVal = isCanDeactivate(guard) ?
163164
guard.canDeactivate(component, currARS, currRSS, futureRSS) :
164-
closestInjector.runInContext<boolean|UrlTree>(
165-
() => guard(component, currARS, currRSS, futureRSS));
165+
closestInjector.runInContext(
166+
() => (guard as CanDeactivateFn<any>)(component, currARS, currRSS, futureRSS));
166167
return wrapIntoObservable(guardVal).pipe(first());
167168
});
168169
return of(canDeactivateObservables).pipe(prioritizedGuardValue());
@@ -180,7 +181,7 @@ export function runCanLoadGuards(
180181
const guard = getTokenOrFunctionIdentity<any>(injectionToken, injector);
181182
const guardVal = isCanLoad(guard) ?
182183
guard.canLoad(route, segments) :
183-
injector.runInContext<boolean|UrlTree>(() => guard(route, segments));
184+
injector.runInContext(() => (guard as CanLoadFn)(route, segments));
184185
return wrapIntoObservable(guardVal);
185186
});
186187

@@ -213,7 +214,7 @@ export function runCanMatchGuards(
213214
const guard = getTokenOrFunctionIdentity(injectionToken, injector);
214215
const guardVal = isCanMatch(guard) ?
215216
guard.canMatch(route, segments) :
216-
injector.runInContext<boolean|UrlTree>(() => guard(route, segments));
217+
injector.runInContext(() => (guard as CanMatchFn)(route, segments));
217218
return wrapIntoObservable(guardVal);
218219
});
219220

0 commit comments

Comments
 (0)