X Tutup
Skip to content

Commit c29ab86

Browse files
committed
refactor(router): improve control flow of descendant route activation
1 parent 6b02cb9 commit c29ab86

File tree

5 files changed

+134
-89
lines changed

5 files changed

+134
-89
lines changed

modules/angular2/src/router/instruction.js

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,32 @@ export class RouteParams {
1616

1717
export class Instruction {
1818
component:any;
19-
_children:StringMap<string, Instruction>;
20-
router:any;
21-
matchedUrl:string;
22-
params:StringMap<string, string>;
19+
_children:Map<string, Instruction>;
20+
21+
// the part of the URL captured by this instruction
22+
capturedUrl:string;
23+
24+
// the part of the URL captured by this instruction and all children
25+
accumulatedUrl:string;
26+
27+
params:Map<string, string>;
2328
reuse:boolean;
2429
cost:number;
2530

2631
constructor({params, component, children, matchedUrl, parentCost}:{params:StringMap, component:any, children:StringMap, matchedUrl:string, cost:number} = {}) {
2732
this.reuse = false;
28-
this.matchedUrl = matchedUrl;
33+
this.capturedUrl = matchedUrl;
34+
this.accumulatedUrl = matchedUrl;
2935
this.cost = parentCost;
3036
if (isPresent(children)) {
3137
this._children = children;
3238
var childUrl;
3339
StringMapWrapper.forEach(this._children, (child, _) => {
34-
childUrl = child.matchedUrl;
40+
childUrl = child.accumulatedUrl;
3541
this.cost += child.cost;
3642
});
3743
if (isPresent(childUrl)) {
38-
this.matchedUrl += childUrl;
44+
this.accumulatedUrl += childUrl;
3945
}
4046
} else {
4147
this._children = StringMapWrapper.create();
@@ -44,45 +50,35 @@ export class Instruction {
4450
this.params = params;
4551
}
4652

47-
getChildInstruction(outletName:string): Instruction {
53+
hasChild(outletName:string):Instruction {
54+
return StringMapWrapper.contains(this._children, outletName);
55+
}
56+
57+
getChild(outletName:string):Instruction {
4858
return StringMapWrapper.get(this._children, outletName);
4959
}
5060

5161
forEachChild(fn:Function): void {
5262
StringMapWrapper.forEach(this._children, fn);
5363
}
5464

55-
mapChildrenAsync(fn):Promise {
56-
return mapObjAsync(this._children, fn);
57-
}
58-
5965
/**
6066
* Does a synchronous, breadth-first traversal of the graph of instructions.
6167
* Takes a function with signature:
6268
* (parent:Instruction, child:Instruction) => {}
6369
*/
6470
traverseSync(fn:Function): void {
65-
this.forEachChild((childInstruction, _) => fn(this, childInstruction));
71+
this.forEachChild(fn);
6672
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
6773
}
6874

69-
/**
70-
* Does an asynchronous, breadth-first traversal of the graph of instructions.
71-
* Takes a function with signature:
72-
* (child:Instruction, parentOutletName:string) => {}
73-
*/
74-
traverseAsync(fn:Function):Promise {
75-
return this.mapChildrenAsync(fn)
76-
.then((_) => this.mapChildrenAsync((childInstruction, _) => childInstruction.traverseAsync(fn)));
77-
}
78-
7975

8076
/**
8177
* Takes a currently active instruction and sets a reuse flag on this instruction
8278
*/
8379
reuseComponentsFrom(oldInstruction:Instruction): void {
84-
this.forEachChild((childInstruction, outletName) => {
85-
var oldInstructionChild = oldInstruction.getChildInstruction(outletName);
80+
this.traverseSync((childInstruction, outletName) => {
81+
var oldInstructionChild = oldInstruction.getChild(outletName);
8682
if (shouldReuseComponent(childInstruction, oldInstructionChild)) {
8783
childInstruction.reuse = true;
8884
}
@@ -104,5 +100,3 @@ function mapObj(obj:StringMap, fn: Function):List {
104100
StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
105101
return result;
106102
}
107-
108-
export var noopInstruction = new Instruction();

modules/angular2/src/router/pipeline.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,6 @@ export class Pipeline {
1111

1212
constructor() {
1313
this.steps = [
14-
instruction => instruction.traverseSync((parentInstruction, childInstruction) => {
15-
childInstruction.router = parentInstruction.router.childRouter(childInstruction.component);
16-
}),
17-
instruction => instruction.router.traverseOutlets((outlet, name) => {
18-
return outlet.canDeactivate(instruction.getChildInstruction(name));
19-
}),
20-
instruction => instruction.router.traverseOutlets((outlet, name) => {
21-
return outlet.canActivate(instruction.getChildInstruction(name));
22-
}),
2314
instruction => instruction.router.activateOutlets(instruction)
2415
];
2516
}

modules/angular2/src/router/router.js

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import {Location} from './location';
1515
* You can see the state of the router by inspecting the read-only field `router.navigating`.
1616
* This may be useful for showing a spinner, for instance.
1717
*
18+
* ## Concepts
19+
* Routers and component instances have a 1:1 correspondence.
20+
*
21+
* The router holds reference to a number of "outlets." An outlet is a placeholder that the
22+
* router dynamically fills in depending on the current URL.
23+
*
24+
* When the router navigates from a URL, it must first recognizes it and serialize it into an `Instruction`.
25+
* The router uses the `RouteRegistry` to get an `Instruction`.
26+
*
1827
* @exportedAs angular2/router
1928
*/
2029
export class Router {
@@ -28,19 +37,16 @@ export class Router {
2837

2938
_pipeline:Pipeline;
3039
_registry:RouteRegistry;
31-
_outlets:Map<any, RouterOutlet>;
32-
_children:Map<any, Router>;
40+
_outlets:Map<any, Outlet>;
3341
_subject:EventEmitter;
34-
_location:Location;
3542

36-
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, parent:Router, hostComponent) {
43+
44+
constructor(registry:RouteRegistry, pipeline:Pipeline, parent:Router, hostComponent:any) {
3745
this.hostComponent = hostComponent;
3846
this.navigating = false;
3947
this.parent = parent;
4048
this.previousUrl = null;
4149
this._outlets = MapWrapper.create();
42-
this._children = MapWrapper.create();
43-
this._location = location;
4450
this._registry = registry;
4551
this._pipeline = pipeline;
4652
this._subject = new EventEmitter();
@@ -51,33 +57,26 @@ export class Router {
5157
/**
5258
* Constructs a child router. You probably don't need to use this unless you're writing a reusable component.
5359
*/
54-
childRouter(outletName = 'default'): Router {
55-
var router = MapWrapper.get(this._children, outletName);
56-
57-
if (isBlank(router)) {
58-
router = new ChildRouter(this, outletName);
59-
MapWrapper.set(this._children, outletName, router);
60-
}
61-
62-
return router;
60+
childRouter(hostComponent:any): Router {
61+
return new ChildRouter(this, hostComponent);
6362
}
6463

6564

6665
/**
6766
* Register an object to notify of route changes. You probably don't need to use this unless you're writing a reusable component.
6867
*/
69-
registerOutlet(outlet:RouterOutlet, name: string = 'default'):Promise {
68+
registerOutlet(outlet:RouterOutlet, name: string = 'default'): Promise {
7069
MapWrapper.set(this._outlets, name, outlet);
7170
if (isPresent(this._currentInstruction)) {
72-
var childInstruction = this._currentInstruction.getChildInstruction(name);
71+
var childInstruction = this._currentInstruction.getChild(name);
7372
return outlet.activate(childInstruction);
7473
}
7574
return PromiseWrapper.resolve(true);
7675
}
7776

7877

7978
/**
80-
* Update the routing configuration and trigger a navigation.
79+
* Dynamically update the routing configuration and trigger a navigation.
8180
*
8281
* # Usage
8382
*
@@ -98,7 +97,6 @@ export class Router {
9897
config(config:any): Promise {
9998
if (config instanceof List) {
10099
config.forEach((configObject) => {
101-
// TODO: this is a hack
102100
this._registry.config(this.hostComponent, configObject);
103101
});
104102
} else {
@@ -110,6 +108,9 @@ export class Router {
110108

111109
/**
112110
* Navigate to a URL. Returns a promise that resolves to the canonical URL for the route.
111+
*
112+
* If the given URL begins with a `/`, router will navigate absolutely.
113+
* If the given URL does not begin with `/`, the router will navigate relative to this component.
113114
*/
114115
navigate(url:string):Promise {
115116
if (this.navigating) {
@@ -124,19 +125,16 @@ export class Router {
124125
return PromiseWrapper.resolve(false);
125126
}
126127

127-
if(isPresent(this._currentInstruction)) {
128+
if (isPresent(this._currentInstruction)) {
128129
matchedInstruction.reuseComponentsFrom(this._currentInstruction);
129130
}
130131

131-
matchedInstruction.router = this;
132132
this._startNavigating();
133133

134-
var result = this._pipeline.process(matchedInstruction)
134+
var result = this.commit(matchedInstruction)
135135
.then((_) => {
136-
this._location.go(matchedInstruction.matchedUrl);
137-
ObservableWrapper.callNext(this._subject, matchedInstruction.matchedUrl);
136+
ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl);
138137
this._finishNavigating();
139-
this._currentInstruction = matchedInstruction;
140138
});
141139

142140
PromiseWrapper.catchError(result, (_) => this._finishNavigating());
@@ -152,6 +150,7 @@ export class Router {
152150
this.navigating = false;
153151
}
154152

153+
155154
/**
156155
* Subscribe to URL updates from the router
157156
*/
@@ -160,25 +159,46 @@ export class Router {
160159
}
161160

162161

163-
activateOutlets(instruction:Instruction):Promise {
164-
return this._queryOutlets((outlet, name) => {
165-
var childInstruction = instruction.getChildInstruction(name);
166-
if (childInstruction.reuse) {
167-
return PromiseWrapper.resolve(true);
162+
/**
163+
*
164+
*/
165+
commit(instruction:Instruction):Promise {
166+
this._currentInstruction = instruction;
167+
168+
// collect all outlets that do not have a corresponding child instruction
169+
// and remove them from the internal map of child outlets
170+
var toDeactivate = ListWrapper.create();
171+
MapWrapper.forEach(this._outlets, (outlet, outletName) => {
172+
if (!instruction.hasChild(outletName)) {
173+
MapWrapper.delete(this._outlets, outletName);
174+
ListWrapper.push(toDeactivate, outlet);
168175
}
169-
return outlet.activate(childInstruction);
170-
})
171-
.then((_) => instruction.mapChildrenAsync((instruction, _) => {
172-
return instruction.router.activateOutlets(instruction);
173-
}));
176+
});
177+
178+
return PromiseWrapper.all(ListWrapper.map(toDeactivate, (outlet) => outlet.deactivate()))
179+
.then((_) => this.activate(instruction));
174180
}
175181

176-
traverseOutlets(fn):Promise {
177-
return this._queryOutlets(fn)
178-
.then((_) => mapObjAsync(this._children, (child, _) => child.traverseOutlets(fn)));
182+
183+
/**
184+
* Recursively remove all components contained by this router's outlets.
185+
* Calls deactivate hooks on all descendant components
186+
*/
187+
deactivate():Promise {
188+
return this._eachOutletAsync((outlet) => outlet.deactivate);
179189
}
180190

181-
_queryOutlets(fn):Promise {
191+
192+
/**
193+
* Recursively activate.
194+
* Calls the "activate" hook on descendant components.
195+
*/
196+
activate(instruction:Instruction):Promise {
197+
return this._eachOutletAsync((outlet, name) => outlet.activate(instruction.getChild(name)));
198+
}
199+
200+
201+
_eachOutletAsync(fn):Promise {
182202
return mapObjAsync(this._outlets, fn);
183203
}
184204

@@ -212,17 +232,26 @@ export class Router {
212232
}
213233

214234
export class RootRouter extends Router {
235+
_location:Location;
236+
215237
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, hostComponent:Type) {
216-
super(registry, pipeline, location, null, hostComponent);
238+
super(registry, pipeline, null, hostComponent);
239+
this._location = location;
217240
this._location.subscribe((change) => this.navigate(change['url']));
218241
this._registry.configFromComponent(hostComponent);
219242
this.navigate(location.path());
220243
}
244+
245+
commit(instruction):Promise {
246+
return super.commit(instruction).then((_) => {
247+
this._location.go(instruction.accumulatedUrl);
248+
});
249+
}
221250
}
222251

223252
class ChildRouter extends Router {
224253
constructor(parent:Router, hostComponent) {
225-
super(parent._registry, parent._pipeline, parent._location, parent, hostComponent);
254+
super(parent._registry, parent._pipeline, parent, hostComponent);
226255
this.parent = parent;
227256
}
228257
}

0 commit comments

Comments
 (0)
X Tutup