/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/router/src/utils/preactivation.ts
233 строки
7 KB
Andrew Scott
refactor(router): Store route injector on ActivatedRoute instance
21 ноя 2025, 01:05
21 ноя 2025, 01:05
1ab461c
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { Injector, ProviderToken, ɵisInjectable as isInjectable, EnvironmentInjector, runInInjectionContext, } from '@angular/core'; import {RunGuardsAndResolvers} from '../models'; import {ChildrenOutletContexts, OutletContext} from '../router_outlet_context'; import { ActivatedRouteSnapshot, equalParamsAndUrlSegments, RouterStateSnapshot, } from '../router_state'; import {equalPath} from '../url_tree'; import {shallowEqual} from '../utils/collection'; import {nodeChildrenAsMap, TreeNode} from '../utils/tree'; export class CanActivate { readonly route: ActivatedRouteSnapshot; constructor(public path: ActivatedRouteSnapshot[]) { this.route = this.path[this.path.length - 1]; } } export class CanDeactivate { constructor( public component: Object | null, public route: ActivatedRouteSnapshot, ) {} } export declare type Checks = { canDeactivateChecks: CanDeactivate[]; canActivateChecks: CanActivate[]; }; export function getAllRouteGuards( future: RouterStateSnapshot, curr: RouterStateSnapshot, parentContexts: ChildrenOutletContexts, ): Checks { const futureRoot = future._root; const currRoot = curr ? curr._root : null; return getChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]); } export function getCanActivateChild( p: ActivatedRouteSnapshot, ): {node: ActivatedRouteSnapshot; guards: any[]} | null { const canActivateChild = p.routeConfig ? p.routeConfig.canActivateChild : null; if (!canActivateChild || canActivateChild.length === 0) return null; return {node: p, guards: canActivateChild}; } export function getTokenOrFunctionIdentity<T>( tokenOrFunction: Function | ProviderToken<T>, injector: Injector, ): Function | T { const NOT_FOUND = Symbol(); const result = injector.get<T | Symbol>(tokenOrFunction, NOT_FOUND); if (result === NOT_FOUND) { if (typeof tokenOrFunction === 'function' && !isInjectable(tokenOrFunction)) { // We think the token is just a function so return it as-is return tokenOrFunction; } else { // This will throw the not found error return injector.get<T>(tokenOrFunction); } } return result as T; } function getChildRouteGuards( futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot> | null, contexts: ChildrenOutletContexts | null, futurePath: ActivatedRouteSnapshot[], checks: Checks = { canDeactivateChecks: [], canActivateChecks: [], }, ): Checks { const prevChildren = nodeChildrenAsMap(currNode); // Process the children of the future route futureNode.children.forEach((c) => { getRouteGuards(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]), checks); delete prevChildren[c.value.outlet]; }); // Process any children left from the current route (not active for the future route) Object.entries(prevChildren).forEach(([k, v]: [string, TreeNode<ActivatedRouteSnapshot>]) => deactivateRouteAndItsChildren(v, contexts!.getContext(k), checks), ); return checks; } function getRouteGuards( futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>, parentContexts: ChildrenOutletContexts | null, futurePath: ActivatedRouteSnapshot[], checks: Checks = { canDeactivateChecks: [], canActivateChecks: [], }, ): Checks { const future = futureNode.value; const curr = currNode ? currNode.value : null; const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null; // reusing the node if (curr && future.routeConfig === curr.routeConfig) { const shouldRun = shouldRunGuardsAndResolvers( curr, future, future.routeConfig!.runGuardsAndResolvers, ); if (shouldRun) { checks.canActivateChecks.push(new CanActivate(futurePath)); } else { // we need to set the data future.data = curr.data; future._resolvedData = curr._resolvedData; } // If we have a component, we need to go through an outlet. if (future.component) { getChildRouteGuards( futureNode, currNode, context ? context.children : null, futurePath, checks, ); // if we have a componentless route, we recurse but keep the same outlet map. } else { getChildRouteGuards(futureNode, currNode, parentContexts, futurePath, checks); } if (shouldRun && context && context.outlet && context.outlet.isActivated) { checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, curr)); } } else { if (curr) { deactivateRouteAndItsChildren(currNode, context, checks); } checks.canActivateChecks.push(new CanActivate(futurePath)); // If we have a component, we need to go through an outlet. if (future.component) { getChildRouteGuards(futureNode, null, context ? context.children : null, futurePath, checks); // if we have a componentless route, we recurse but keep the same outlet map. } else { getChildRouteGuards(futureNode, null, parentContexts, futurePath, checks); } } return checks; } function shouldRunGuardsAndResolvers( curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot, mode: RunGuardsAndResolvers | undefined, ): boolean { if (typeof mode === 'function') { return runInInjectionContext(future._environmentInjector, () => mode(curr, future)); } switch (mode) { case 'pathParamsChange': return !equalPath(curr.url, future.url); case 'pathParamsOrQueryParamsChange': return ( !equalPath(curr.url, future.url) || !shallowEqual(curr.queryParams, future.queryParams) ); case 'always': return true; case 'paramsOrQueryParamsChange': return ( !equalParamsAndUrlSegments(curr, future) || !shallowEqual(curr.queryParams, future.queryParams) ); case 'paramsChange': default: return !equalParamsAndUrlSegments(curr, future); } } function deactivateRouteAndItsChildren( route: TreeNode<ActivatedRouteSnapshot>, context: OutletContext | null, checks: Checks, ): void { const children = nodeChildrenAsMap(route); const r = route.value; Object.entries(children).forEach(([childName, node]) => { if (!r.component) { deactivateRouteAndItsChildren(node, context, checks); } else if (context) { deactivateRouteAndItsChildren(node, context.children.getContext(childName), checks); } else { deactivateRouteAndItsChildren(node, null, checks); } }); if (!r.component) { checks.canDeactivateChecks.push(new CanDeactivate(null, r)); } else if (context && context.outlet && context.outlet.isActivated) { checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r)); } else { checks.canDeactivateChecks.push(new CanDeactivate(null, r)); } }