/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/router/src/operators/activate_routes.ts
239 строк
9 KB
Andrew Scott
refactor(router): Add handling for ActivatedRoute-scoped injector
17 июн 2026, 21:39
17 июн 2026, 21:39
1e79dd3
Код
Авторство
О чём код?
/** * @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 {ActivationEnd, ChildActivationEnd, Event} from '../events'; import type {DetachedRouteHandleInternal, RouteReuseStrategy} from '../route_reuse_strategy'; import type {ChildrenOutletContexts} from '../router_outlet_context'; import {ActivatedRoute, advanceActivatedRoute, RouterState} from '../router_state'; import {nodeChildrenAsMap, TreeNode} from '../utils/tree'; let warnedAboutUnsupportedInputBinding = false; export class ActivateRoutes { constructor( private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState, private currState: RouterState, private forwardEvent: (evt: Event) => void, private inputBindingEnabled: boolean, ) {} activate(parentContexts: ChildrenOutletContexts): void { const futureRoot = this.futureState._root; const currRoot = this.currState ? this.currState._root : null; this.deactivateChildRoutes(futureRoot, currRoot, parentContexts); advanceActivatedRoute(this.futureState.root); this.activateChildRoutes(futureRoot, currRoot, parentContexts); } // De-activate the child route that are not re-used for the future state private deactivateChildRoutes( futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute> | null, contexts: ChildrenOutletContexts, ): void { const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(currNode); // Recurse on the routes active in the future state to de-activate deeper children futureNode.children.forEach((futureChild) => { const childOutletName = futureChild.value.outlet; this.deactivateRoutes(futureChild, children[childOutletName], contexts); delete children[childOutletName]; }); // De-activate the routes that will not be re-used Object.values(children).forEach((v: TreeNode<ActivatedRoute>) => { this.deactivateRouteAndItsChildren(v, contexts); }); } private deactivateRoutes( futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, parentContext: ChildrenOutletContexts, ): void { const future = futureNode.value; const curr = currNode ? currNode.value : null; if (future === curr) { // Reusing the node, check to see if the children need to be de-activated if (future.component) { // If we have a normal route, we need to go through an outlet. const context = parentContext.getContext(future.outlet); if (context) { this.deactivateChildRoutes(futureNode, currNode, context.children); } } else { // if we have a componentless route, we recurse but keep the same outlet map. this.deactivateChildRoutes(futureNode, currNode, parentContext); } } else { if (curr) { // Deactivate the current route which will not be re-used this.deactivateRouteAndItsChildren(currNode, parentContext); } } } private deactivateRouteAndItsChildren( route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts, ): void { // If there is no component, the Route is never attached to an outlet (because there is no // component to attach). if (route.value.component && this.routeReuseStrategy.shouldDetach(route.value.snapshot)) { this.detachAndStoreRouteSubtree(route, parentContexts); } else { this.deactivateRouteAndOutlet(route, parentContexts); } } private detachAndStoreRouteSubtree( route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts, ): void { const context = parentContexts.getContext(route.value.outlet); const contexts = context && route.value.component ? context.children : parentContexts; const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(route); for (const treeNode of Object.values(children)) { this.deactivateRouteAndItsChildren(treeNode, contexts); } if (context && context.outlet) { const componentRef = context.outlet.detach(); const contexts = context.children.onOutletDeactivated(); this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route, contexts}); } } private deactivateRouteAndOutlet( route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts, ): void { const context = parentContexts.getContext(route.value.outlet); // The context could be `null` if we are on a componentless route but there may still be // children that need deactivating. const contexts = context && route.value.component ? context.children : parentContexts; const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(route); for (const treeNode of Object.values(children)) { this.deactivateRouteAndItsChildren(treeNode, contexts); } if (context) { if (context.outlet) { // Destroy the component context.outlet.deactivate(); // Destroy the contexts for all the outlets that were in the component context.children.onOutletDeactivated(); } // Clear the information about the attached component on the context but keep the reference to // the outlet. Clear even if outlet was not yet activated to avoid activating later with old // info context.attachRef = null; context.route = null; } // Destroy `_localInjector` here when the route is // unmounted by the Router. This method (`deactivateRouteAndOutlet`) is // skipped when a route is being detached for `RouteReuseStrategy`, preserving // its injector. Those preserved injectors are eventually managed and destroyed // manually via `destroyDetachedRouteHandle()` or if the route is deactivated later rather than detached. route.value._localInjector?.destroy(); } private activateChildRoutes( futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute> | null, contexts: ChildrenOutletContexts, ): void { const children: {[outlet: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(currNode); futureNode.children.forEach((c) => { this.activateRoutes(c, children[c.value.outlet], contexts); this.forwardEvent(new ActivationEnd(c.value.snapshot)); }); if (futureNode.children.length) { this.forwardEvent(new ChildActivationEnd(futureNode.value.snapshot)); } } private activateRoutes( futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts, ): void { const future = futureNode.value; const curr = currNode ? currNode.value : null; advanceActivatedRoute(future); // reusing the node if (future === curr) { if (future.component) { // If we have a normal route, we need to go through an outlet. const context = parentContexts.getOrCreateContext(future.outlet); this.activateChildRoutes(futureNode, currNode, context.children); } else { // if we have a componentless route, we recurse but keep the same outlet map. this.activateChildRoutes(futureNode, currNode, parentContexts); } } else { if (future.component) { // if we have a normal route, we need to place the component into the outlet and recurse. const context = parentContexts.getOrCreateContext(future.outlet); if (this.routeReuseStrategy.shouldAttach(future.snapshot)) { const stored = <DetachedRouteHandleInternal>( this.routeReuseStrategy.retrieve(future.snapshot) ); this.routeReuseStrategy.store(future.snapshot, null); context.children.onOutletReAttached(stored.contexts); context.attachRef = stored.componentRef; context.route = stored.route.value; if (context.outlet) { // Attach right away when the outlet has already been instantiated // Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated context.outlet.attach(stored.componentRef, stored.route.value); } advanceActivatedRoute(stored.route.value); this.activateChildRoutes(futureNode, null, context.children); } else { context.attachRef = null; context.route = future; if (context.outlet) { // Activate the outlet when it has already been instantiated // Otherwise it will get activated from its `ngOnInit` when instantiated context.outlet.activateWith(future, context.injector); } this.activateChildRoutes(futureNode, null, context.children); } } else { // if we have a componentless route, we recurse but keep the same outlet map. this.activateChildRoutes(futureNode, null, parentContexts); } } if (typeof ngDevMode === 'undefined' || ngDevMode) { const context = parentContexts.getOrCreateContext(future.outlet); const outlet = context.outlet; if ( outlet && this.inputBindingEnabled && !outlet.supportsBindingToComponentInputs && !warnedAboutUnsupportedInputBinding ) { console.warn( `'withComponentInputBinding' feature is enabled but ` + `this application is using an outlet that may not support binding to component inputs.`, ); warnedAboutUnsupportedInputBinding = true; } } } }