Skip to content

Commit 0abb67a

Browse files
committed
feat(router): allow guards and resolvers to be plain functions (#46684)
The current Router APIs require guards/resolvers to be present in the DI tree. This is because we want to treat all guards/resolvers equally and some may require dependencies. This requirement results in quite a lot of boilerplate for guards. Here are two examples: ``` const MY_GUARD = new InjectionToken<any>('my_guard'); … providers: {provide: MY_GUARD, useValue: () => window.someGlobalState} … const route = {path: 'somePath', canActivate: [MY_GUARD]} ``` ``` @Injectable({providedIn: 'root'}) export class MyGuardWithDependency { constructor(private myDep: MyDependency) {} canActivate() { return myDep.canActivate(); } } … const route = {path: 'somePath', canActivate: [MyGuardWithDependency]} ``` Notice that even when we want to write a simple guard that has no dependencies as in the first example, we still have to write either an InjectionToken or an Injectable class. With this commit router guards and resolvers can be plain old functions. For example: ``` const route = {path: 'somePath', component: EditCmp, canDeactivate: [(component: EditCmp) => !component.hasUnsavedChanges]} ``` Additionally, these functions can still use Angular DI with `inject` from `@angular/core`. ``` const route = {path: 'somePath', canActivate: [() => inject(MyDependency).canActivate()]} ``` PR Close #46684
1 parent 0920a15 commit 0abb67a

File tree

18 files changed

+527
-258
lines changed

18 files changed

+527
-258
lines changed

goldens/public-api/router/index.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,30 @@ export interface CanActivateChild {
125125
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
126126
}
127127

128+
// @public
129+
export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
130+
131+
// @public
132+
export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
133+
128134
// @public
129135
export interface CanDeactivate<T> {
130136
// (undocumented)
131137
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
132138
}
133139

140+
// @public
141+
export type CanDeactivateFn<T> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
142+
134143
// @public
135144
export interface CanLoad {
136145
// (undocumented)
137146
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
138147
}
139148

149+
// @public
150+
export type CanLoadFn = (route: Route, segments: UrlSegment[]) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
151+
140152
// @public
141153
export interface CanMatch {
142154
// (undocumented)
@@ -505,7 +517,7 @@ export interface Resolve<T> {
505517

506518
// @public
507519
export type ResolveData = {
508-
[key: string | symbol]: any;
520+
[key: string | symbol]: any | ResolveFn<unknown>;
509521
};
510522

511523
// @public
@@ -525,6 +537,9 @@ export class ResolveEnd extends RouterEvent {
525537
urlAfterRedirects: string;
526538
}
527539

540+
// @public
541+
export type ResolveFn<T> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<T> | Promise<T> | T;
542+
528543
// @public
529544
export class ResolveStart extends RouterEvent {
530545
constructor(
@@ -544,11 +559,11 @@ export class ResolveStart extends RouterEvent {
544559

545560
// @public
546561
export interface Route {
547-
canActivate?: any[];
548-
canActivateChild?: any[];
549-
canDeactivate?: any[];
550-
canLoad?: any[];
551-
canMatch?: Array<Type<CanMatch> | InjectionToken<CanMatchFn>>;
562+
canActivate?: Array<CanActivateFn | any>;
563+
canActivateChild?: Array<CanActivateChildFn | any>;
564+
canDeactivate?: Array<CanDeactivateFn<any> | any>;
565+
canLoad?: Array<CanLoadFn | any>;
566+
canMatch?: Array<Type<CanMatch> | InjectionToken<CanMatchFn> | CanMatchFn>;
552567
children?: Routes;
553568
component?: Type<any>;
554569
data?: Data;
@@ -562,7 +577,7 @@ export interface Route {
562577
redirectTo?: string;
563578
resolve?: ResolveData;
564579
runGuardsAndResolvers?: RunGuardsAndResolvers;
565-
title?: string | Type<Resolve<string>>;
580+
title?: string | Type<Resolve<string>> | ResolveFn<string>;
566581
}
567582

568583
// @public

goldens/size-tracking/integration-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"cli-hello-world-lazy": {
3434
"uncompressed": {
3535
"runtime": 2835,
36-
"main": 238214,
36+
"main": 238737,
3737
"polyfills": 33842,
3838
"src_app_lazy_lazy_module_ts": 780
3939
}

packages/core/src/application_ref.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ import {InternalViewRef, ViewRef} from './linker/view_ref';
3434
import {isComponentResourceResolutionQueueEmpty, resolveComponentResources} from './metadata/resource_loading';
3535
import {assertNgModuleType} from './render3/assert';
3636
import {ComponentFactory as R3ComponentFactory} from './render3/component_ref';
37+
import {isStandalone} from './render3/definition';
3738
import {assertStandaloneComponentType} from './render3/errors';
3839
import {setLocaleId} from './render3/i18n/i18n_locale_id';
3940
import {setJitOptions} from './render3/jit/jit_options';
40-
import {isStandalone} from './render3/jit/module';
4141
import {createEnvironmentInjector, NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
4242
import {publishDefaultGlobalUtils as _publishDefaultGlobalUtils} from './render3/util/global_utils';
4343
import {TESTABILITY} from './testability/testability';

packages/core/src/core_render3_private_export.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export {
2424
export {
2525
NG_INJ_DEF as ɵNG_INJ_DEF,
2626
NG_PROV_DEF as ɵNG_PROV_DEF,
27+
isInjectable as ɵisInjectable,
2728
} from './di/interface/defs';
2829
export {createInjector as ɵcreateInjector} from './di/create_injector';
2930
export {
@@ -252,9 +253,7 @@ export {
252253
export {
253254
compilePipe as ɵcompilePipe,
254255
} from './render3/jit/pipe';
255-
export {
256-
isStandalone as ɵisStandalone,
257-
} from './render3/jit/module';
256+
export { isStandalone as ɵisStandalone} from './render3/definition';
258257
export { Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent } from './render3/profiler';
259258
export {
260259
publishDefaultGlobalUtils as ɵpublishDefaultGlobalUtils

packages/core/src/di/interface/defs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ export function getInjectableDef<T>(type: any): ɵɵInjectableDeclaration<T>|nul
190190
return getOwnDefinition(type, NG_PROV_DEF) || getOwnDefinition(type, NG_INJECTABLE_DEF);
191191
}
192192

193+
export function isInjectable(type: any): boolean {
194+
return getInjectableDef(type) !== null;
195+
}
196+
193197
/**
194198
* Return definition only if it is defined directly on `type` and is not inherited from a base
195199
* class of `type`.

packages/core/src/render3/definition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {ChangeDetectionStrategy} from '../change_detection/constants';
10+
import {NG_PROV_DEF} from '../di/interface/defs';
1011
import {Mutable, Type} from '../interface/type';
1112
import {NgModuleDef, NgModuleType} from '../metadata/ng_module_def';
1213
import {SchemaMetadata} from '../metadata/schema';
@@ -743,6 +744,11 @@ export function getPipeDef<T>(type: any): PipeDef<T>|null {
743744
return type[NG_PIPE_DEF] || null;
744745
}
745746

747+
export function isStandalone<T>(type: Type<T>): boolean {
748+
const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type);
749+
return def !== null ? def.standalone : false;
750+
}
751+
746752
export function getNgModuleDef<T>(type: any, throwNotFound: true): NgModuleDef<T>;
747753
export function getNgModuleDef<T>(type: any): NgModuleDef<T>|null;
748754
export function getNgModuleDef<T>(type: any, throwNotFound?: boolean): NgModuleDef<T>|null {

packages/core/src/render3/jit/module.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {NgModuleDef, NgModuleTransitiveScopes, NgModuleType} from '../../metadat
1919
import {deepForEach, flatten} from '../../util/array_utils';
2020
import {assertDefined} from '../../util/assert';
2121
import {EMPTY_ARRAY} from '../../util/empty';
22-
import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition';
22+
import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef, isStandalone} from '../definition';
2323
import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_MOD_DEF, NG_PIPE_DEF} from '../fields';
2424
import {ComponentDef} from '../interfaces/definition';
2525
import {maybeUnwrapFn} from '../util/misc_utils';
@@ -197,11 +197,6 @@ export function compileNgModuleDefs(
197197
});
198198
}
199199

200-
export function isStandalone<T>(type: Type<T>) {
201-
const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type);
202-
return def !== null ? def.standalone : false;
203-
}
204-
205200
export function generateStandaloneInDeclarationsError(type: Type<any>, location: string) {
206201
const prefix = `Unexpected "${stringifyForError(type)}" found in the "declarations" array of the`;
207202
const suffix = `"${stringifyForError(type)}" is marked as standalone and can't be declared ` +

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@
13141314
"name": "getTView"
13151315
},
13161316
{
1317-
"name": "getToken"
1317+
"name": "getTokenOrFunctionIdentity"
13181318
},
13191319
{
13201320
"name": "getViewRefs"

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, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
15-
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, CanMatch, CanMatchFn, Data, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, QueryParamsHandling, Resolve, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
15+
export {CanActivate, CanActivateChild, CanActivateChildFn, CanActivateFn, CanDeactivate, CanDeactivateFn, CanLoad, CanLoadFn, CanMatch, CanMatchFn, Data, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, QueryParamsHandling, Resolve, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
1616
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
1717
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
1818
export {Navigation, NavigationExtras, Router, UrlCreationOptions} from './router';

0 commit comments

Comments
 (0)