Я предполагаю, что route
является введенным ActivatedRoute
.
Каждый ActivatedRoute
связан с маршрутизируемым компонентом, и когда происходит изменение маршрута, текущий отображаемый компонент будет уничтожен, как и связанный с ним ActivatedRoute
, вот почему вы не получите уведомление complete
.
вот как ActivatedRoute
создано:
function createActivatedRoute(c: ActivatedRouteSnapshot) {
return new ActivatedRoute(
new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
}
Теперь, как ActivatedRoute
связаны с маршрутизируемым компонентом?
Предполагая, что у вас есть конфигурация, которая выглядит следующим образом:
{
path: 'a/:id',
component: AComponent,
children: [
{
path: 'b',
component: BComponent,
},
{
path: 'c',
component: CComponent,
},
]
}
и выданный URL, например a/123/b
в итоге вы получите дерево из ActivatedRoute
s:
APP
|
A
|
B
Всякий раз, когда вы планируете навигацию (например, router.navigateToUrl()
), она должна пройти несколько важных этапов:
- применить перенаправления: проверка перенаправлений; загрузка отложенных модулей; найти
NoMatch
ошибок
- распознать: создание дерева
ActivatedRouteSnapshot
- предварительная активация: сравнение полученного дерева с текущим; на этом этапе также собираются
canActivate
и canDeactivate
охранников на основе найденных различий
- бегущие охранники
- создать состояние маршрутизатора: где создается дерево
ActivatedRoute
активация маршрутов: это вишенка на торте и место, где используется дерево ActivatedRoute
.
Также важно упомянуть роль, которую играет router-outlet
.
Angular отслеживает router-outlet
с помощью объекта Map
.
вот что происходит, когда у вас есть <router-outlet></router-outlet>
в вашем приложении:
@Directive({selector: 'router-outlet', exportAs: 'outlet'})
export class RouterOutlet implements OnDestroy, OnInit {
private activated: ComponentRef<any>|null = null;
private _activatedRoute: ActivatedRoute|null = null;
private name: string;
@Output('activate') activateEvents = new EventEmitter<any>();
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
constructor(
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
private resolver: ComponentFactoryResolver, @Attribute('name') name: string,
private changeDetector: ChangeDetectorRef) {
this.name = name || PRIMARY_OUTLET;
parentContexts.onChildOutletCreated(this.name, this);
}
}
Обратите внимание на наличие activated
(это компонент) и _activatedRoute
!.
И здесь находятся соответствующие биты из ChildrenOutletContexts
:
export class ChildrenOutletContexts {
// contexts for child outlets, by name.
private contexts = new Map<string, OutletContext>();
/** Called when a `RouterOutlet` directive is instantiated */
onChildOutletCreated(childName: string, outlet: RouterOutlet): void {
const context = this.getOrCreateContext(childName);
context.outlet = outlet;
this.contexts.set(childName, context);
}
}
где childName
по умолчанию 'primary'
. Пока сосредоточьте свое внимание только на части context.outlet
.
Итак, для нашей конфигурации маршрута:
{
path: 'a/:id',
component: AComponent,
children: [
{
path: 'b',
component: BComponent,
},
{
path: 'c',
component: CComponent,
},
]
}
router-outlet
Map
будет выглядеть так (примерно):
{
primary: { // Where `AComponent` resides [1]
children: {
// Here `AComponent`'s children reside [2]
primary: { children: { } }
}
}
}
Теперь давайте посмотрим, как маршрут активирован:
// This block of code will be run for [1] and [2] (in this order!)
const context = parentContexts.getOrCreateContext(future.outlet);
/* ... */
const config = parentLoadedConfig(future.snapshot);
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
context.attachRef = null;
context.route = future;
context.resolver = cmpFactoryResolver;
if (context.outlet) {
context.outlet.activateWith(future, cmpFactoryResolver);
}
this.activateChildRoutes(futureNode, null, context.children);
context.outlet.activateWith(future, cmpFactoryResolver);
что мы ищем (где outlet
— экземпляр директивы RouterOutlet
):
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
this._activatedRoute = activatedRoute;
const snapshot = activatedRoute._futureSnapshot;
const component = <any>snapshot.routeConfig!.component;
resolver = resolver || this.resolver;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
this.activated = this.location.createComponent(factory, this.location.length, injector);
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
this.changeDetector.markForCheck();
this.activateEvents.emit(this.activated.instance);
}
Обратите внимание, что this.activated
содержит маршрутизируемый компонент (например, AComponent
), а this._activatedRoute
содержит ActivatedRoute
для этого компонента.
Теперь посмотрим, что происходит когда мы переходим к другому маршруту и это текущее представление уничтожается:
deactivateRouteAndOutlet(
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
const context = parentContexts.getContext(route.value.outlet);
if (context) {
const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
const contexts = route.value.component ? context.children : parentContexts;
// Deactivate children first
forEach(children, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, contexts));
if (context.outlet) {
// Destroy the component
context.outlet.deactivate();
// Destroy the contexts for all the outlets that were in the component
context.children.onOutletDeactivated();
}
}
}
где RouterOutlet.deactivate()
выглядит вот так< /а>:
deactivate(): void {
if (this.activated) {
const c = this.component;
this.activated.destroy(); // Destroying the current component
this.activated = null;
// Nulling out the activated route - so no `complete` notification
this._activatedRoute = null;
this.deactivateEvents.emit(c);
}
}
person
Andrei Gătej
schedule
08.06.2020