Skip to content

Commit 198edb3

Browse files
vicbmatsko
authored andcommitted
refactor(router): don't run the change detection every time an outlet is activated
1 parent 44d48d9 commit 198edb3

11 files changed

Lines changed: 298 additions & 231 deletions

File tree

‎packages/router/src/directives/router_outlet.ts‎

Lines changed: 32 additions & 47 deletions
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,39 @@ 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

4847
constructor(
49-
private parentOutletMap: RouterOutletMap, private location: ViewContainerRef,
48+
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
5049
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) {
51-
this._outletName = name || PRIMARY_OUTLET;
52-
parentOutletMap.registerOutlet(this._outletName, this);
50+
this.name = name || PRIMARY_OUTLET;
51+
parentContexts.onChildOutletCreated(this.name, this);
5352
}
5453

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

5773
/** @deprecated since v4 **/
5874
get locationInjector(): Injector { return this.location.injector; }
@@ -102,65 +118,34 @@ export class RouterOutlet implements OnDestroy {
102118
}
103119
}
104120

105-
/** @deprecated since v4, use {@link #activateWith} */
106-
activate(
107-
activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector,
108-
providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void {
121+
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
109122
if (this.isActivated) {
110123
throw new Error('Cannot activate an already activated outlet');
111124
}
112-
113-
this.outletMap = outletMap;
114125
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-
138126
const snapshot = activatedRoute._futureSnapshot;
139127
const component = <any>snapshot._routeConfig !.component;
140-
141128
resolver = resolver || this.resolver;
142129
const factory = resolver.resolveComponentFactory(component);
143-
144-
const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector);
145-
130+
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
131+
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
146132
this.activated = this.location.createComponent(factory, this.location.length, injector);
147-
this.activated.changeDetectorRef.detectChanges();
148-
149133
this.activateEvents.emit(this.activated.instance);
150134
}
151135
}
152136

153137
class OutletInjector implements Injector {
154138
constructor(
155-
private route: ActivatedRoute, private map: RouterOutletMap, private parent: Injector) {}
139+
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
140+
private parent: Injector) {}
156141

157142
get(token: any, notFoundValue?: any): any {
158143
if (token === ActivatedRoute) {
159144
return this.route;
160145
}
161146

162-
if (token === RouterOutletMap) {
163-
return this.map;
147+
if (token === ChildrenOutletContexts) {
148+
return this.childContexts;
164149
}
165150

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

‎packages/router/src/index.ts‎

Lines changed: 1 addition & 1 deletion
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, OutletContext} 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‎

Lines changed: 7 additions & 1 deletion
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)