Skip to content

Commit 62b0014

Browse files
committed
refactor(router): don't run the change detection every time an outlet is activated
1 parent e69bce9 commit 62b0014

File tree

11 files changed

+280
-232
lines changed

11 files changed

+280
-232
lines changed

packages/router/src/directives/router_outlet.ts

+33-47
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Attribute, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core';
10-
import {RouterOutletMap} from '../router_outlet_map';
9+
import {Attribute, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core';
10+
import {ChildrenOutletContexts} from '../router_outlet_context';
1111
import {ActivatedRoute} from '../router_state';
1212
import {PRIMARY_OUTLET} from '../shared';
1313

@@ -36,23 +36,40 @@ import {PRIMARY_OUTLET} from '../shared';
3636
* @stable
3737
*/
3838
@Directive({selector: 'router-outlet'})
39-
export class RouterOutlet implements OnDestroy {
39+
export class RouterOutlet implements OnDestroy, OnInit {
4040
private activated: ComponentRef<any>|null = null;
4141
private _activatedRoute: ActivatedRoute|null = null;
42-
private _outletName: string;
43-
public outletMap: RouterOutletMap;
42+
private name: string;
4443

4544
@Output('activate') activateEvents = new EventEmitter<any>();
4645
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
4746

47+
/** @internal */
4848
constructor(
49-
private parentOutletMap: RouterOutletMap, private location: ViewContainerRef,
49+
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
5050
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) {
51-
this._outletName = name || PRIMARY_OUTLET;
52-
parentOutletMap.registerOutlet(this._outletName, this);
51+
this.name = name || PRIMARY_OUTLET;
52+
parentContexts.onChildOutletCreated(this.name, this);
5353
}
5454

55-
ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this._outletName); }
55+
ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(this.name); }
56+
57+
ngOnInit(): void {
58+
if (!this.activated) {
59+
// If the outlet was not instantiated at the time the route got activated we need to populate
60+
// the outlet when it is initialized.
61+
const context = this.parentContexts.getContext(this.name);
62+
if (context && context.route) {
63+
if (context.attachRef) {
64+
// `attachRef` is populated when there is an existing component to mount
65+
this.attach(context.attachRef, context.route);
66+
} else {
67+
// otherwise the component defined in the configuration is created
68+
this.activateWith(context.route, context.resolver || null);
69+
}
70+
}
71+
}
72+
}
5673

5774
/** @deprecated since v4 **/
5875
get locationInjector(): Injector { return this.location.injector; }
@@ -102,65 +119,34 @@ export class RouterOutlet implements OnDestroy {
102119
}
103120
}
104121

105-
/** @deprecated since v4, use {@link #activateWith} */
106-
activate(
107-
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
108-
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
122+
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
109123
if (this.isActivated) {
110124
throw new Error('Cannot activate an already activated outlet');
111125
}
112-
113-
this.outletMap = outletMap;
114126
this._activatedRoute = activatedRoute;
115-
116-
const snapshot = activatedRoute._futureSnapshot;
117-
const component: any = <any>snapshot._routeConfig !.component;
118-
const factory = resolver.resolveComponentFactory(component) !;
119-
120-
const inj = ReflectiveInjector.fromResolvedProviders(providers, injector);
121-
122-
this.activated = this.location.createComponent(factory, this.location.length, inj, []);
123-
this.activated.changeDetectorRef.detectChanges();
124-
125-
this.activateEvents.emit(this.activated.instance);
126-
}
127-
128-
activateWith(
129-
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null,
130-
outletMap: RouterOutletMap) {
131-
if (this.isActivated) {
132-
throw new Error('Cannot activate an already activated outlet');
133-
}
134-
135-
this.outletMap = outletMap;
136-
this._activatedRoute = activatedRoute;
137-
138127
const snapshot = activatedRoute._futureSnapshot;
139128
const component = <any>snapshot._routeConfig !.component;
140-
141129
resolver = resolver || this.resolver;
142130
const factory = resolver.resolveComponentFactory(component);
143-
144-
const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector);
145-
131+
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
132+
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
146133
this.activated = this.location.createComponent(factory, this.location.length, injector);
147-
this.activated.changeDetectorRef.detectChanges();
148-
149134
this.activateEvents.emit(this.activated.instance);
150135
}
151136
}
152137

153138
class OutletInjector implements Injector {
154139
constructor(
155-
private route: ActivatedRoute, private map: RouterOutletMap, private parent: Injector) {}
140+
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
141+
private parent: Injector) {}
156142

157143
get(token: any, notFoundValue?: any): any {
158144
if (token === ActivatedRoute) {
159145
return this.route;
160146
}
161147

162-
if (token === RouterOutletMap) {
163-
return this.map;
148+
if (token === ChildrenOutletContexts) {
149+
return this.childContexts;
164150
}
165151

166152
return this.parent.get(token, notFoundValue);

packages/router/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
1717
export {NavigationExtras, Router} from './router';
1818
export {ROUTES} from './router_config_loader';
1919
export {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
20-
export {RouterOutletMap} from './router_outlet_map';
20+
export {ChildrenOutletContexts} from './router_outlet_context';
2121
export {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
2222
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
2323
export {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';

packages/router/src/route_reuse_strategy.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {ComponentRef} from '@angular/core';
1010

11+
import {OutletContext} from './router_outlet_context';
1112
import {ActivatedRoute, ActivatedRouteSnapshot} from './router_state';
1213
import {TreeNode} from './utils/tree';
1314

@@ -23,6 +24,7 @@ export type DetachedRouteHandle = {};
2324

2425
/** @internal */
2526
export type DetachedRouteHandleInternal = {
27+
contexts: Map<string, OutletContext>,
2628
componentRef: ComponentRef<any>,
2729
route: TreeNode<ActivatedRoute>,
2830
};
@@ -36,7 +38,11 @@ export abstract class RouteReuseStrategy {
3638
/** Determines if this route (and its subtree) should be detached to be reused later */
3739
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
3840

39-
/** Stores the detached route */
41+
/**
42+
* Stores the detached route.
43+
*
44+
* Storing a `null` value should erase the previously stored value.
45+
*/
4046
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle|null): void;
4147

4248
/** Determines if this route (and its subtree) should be reattached */

0 commit comments

Comments
 (0)