UNPKG

24.5 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || function (d, b) {
3 for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
4 function __() { this.constructor = d; }
5 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
6};
7var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
8 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
9 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
10 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
11 return c > 3 && r && Object.defineProperty(target, key, r), r;
12};
13var __metadata = (this && this.__metadata) || function (k, v) {
14 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
15};
16var __param = (this && this.__param) || function (paramIndex, decorator) {
17 return function (target, key) { decorator(target, key, paramIndex); }
18};
19var common_1 = require('@angular/common');
20var core_1 = require('@angular/core');
21var async_1 = require('../src/facade/async');
22var collection_1 = require('../src/facade/collection');
23var exceptions_1 = require('../src/facade/exceptions');
24var lang_1 = require('../src/facade/lang');
25var instruction_1 = require('./instruction');
26var route_lifecycle_reflector_1 = require('./lifecycle/route_lifecycle_reflector');
27var route_registry_1 = require('./route_registry');
28var _resolveToTrue = async_1.PromiseWrapper.resolve(true);
29var _resolveToFalse = async_1.PromiseWrapper.resolve(false);
30/**
31 * The `Router` is responsible for mapping URLs to components.
32 *
33 * You can see the state of the router by inspecting the read-only field `router.navigating`.
34 * This may be useful for showing a spinner, for instance.
35 *
36 * ## Concepts
37 *
38 * Routers and component instances have a 1:1 correspondence.
39 *
40 * The router holds reference to a number of {@link RouterOutlet}.
41 * An outlet is a placeholder that the router dynamically fills in depending on the current URL.
42 *
43 * When the router navigates from a URL, it must first recognize it and serialize it into an
44 * `Instruction`.
45 * The router uses the `RouteRegistry` to get an `Instruction`.
46 */
47var Router = (function () {
48 function Router(registry, parent, hostComponent, root) {
49 this.registry = registry;
50 this.parent = parent;
51 this.hostComponent = hostComponent;
52 this.root = root;
53 this.navigating = false;
54 /**
55 * The current `Instruction` for the router
56 */
57 this.currentInstruction = null;
58 this._currentNavigation = _resolveToTrue;
59 this._outlet = null;
60 this._auxRouters = new collection_1.Map();
61 this._subject = new async_1.EventEmitter();
62 }
63 /**
64 * Constructs a child router. You probably don't need to use this unless you're writing a reusable
65 * component.
66 */
67 Router.prototype.childRouter = function (hostComponent) {
68 return this._childRouter = new ChildRouter(this, hostComponent);
69 };
70 /**
71 * Constructs a child router. You probably don't need to use this unless you're writing a reusable
72 * component.
73 */
74 Router.prototype.auxRouter = function (hostComponent) { return new ChildRouter(this, hostComponent); };
75 /**
76 * Register an outlet to be notified of primary route changes.
77 *
78 * You probably don't need to use this unless you're writing a reusable component.
79 */
80 Router.prototype.registerPrimaryOutlet = function (outlet) {
81 if (lang_1.isPresent(outlet.name)) {
82 throw new exceptions_1.BaseException("registerPrimaryOutlet expects to be called with an unnamed outlet.");
83 }
84 if (lang_1.isPresent(this._outlet)) {
85 throw new exceptions_1.BaseException("Primary outlet is already registered.");
86 }
87 this._outlet = outlet;
88 if (lang_1.isPresent(this.currentInstruction)) {
89 return this.commit(this.currentInstruction, false);
90 }
91 return _resolveToTrue;
92 };
93 /**
94 * Unregister an outlet (because it was destroyed, etc).
95 *
96 * You probably don't need to use this unless you're writing a custom outlet implementation.
97 */
98 Router.prototype.unregisterPrimaryOutlet = function (outlet) {
99 if (lang_1.isPresent(outlet.name)) {
100 throw new exceptions_1.BaseException("registerPrimaryOutlet expects to be called with an unnamed outlet.");
101 }
102 this._outlet = null;
103 };
104 /**
105 * Register an outlet to notified of auxiliary route changes.
106 *
107 * You probably don't need to use this unless you're writing a reusable component.
108 */
109 Router.prototype.registerAuxOutlet = function (outlet) {
110 var outletName = outlet.name;
111 if (lang_1.isBlank(outletName)) {
112 throw new exceptions_1.BaseException("registerAuxOutlet expects to be called with an outlet with a name.");
113 }
114 var router = this.auxRouter(this.hostComponent);
115 this._auxRouters.set(outletName, router);
116 router._outlet = outlet;
117 var auxInstruction;
118 if (lang_1.isPresent(this.currentInstruction) &&
119 lang_1.isPresent(auxInstruction = this.currentInstruction.auxInstruction[outletName])) {
120 return router.commit(auxInstruction);
121 }
122 return _resolveToTrue;
123 };
124 /**
125 * Given an instruction, returns `true` if the instruction is currently active,
126 * otherwise `false`.
127 */
128 Router.prototype.isRouteActive = function (instruction) {
129 var router = this;
130 var currentInstruction = this.currentInstruction;
131 if (lang_1.isBlank(currentInstruction)) {
132 return false;
133 }
134 // `instruction` corresponds to the root router
135 while (lang_1.isPresent(router.parent) && lang_1.isPresent(instruction.child)) {
136 router = router.parent;
137 instruction = instruction.child;
138 }
139 var reason = true;
140 // check the instructions in depth
141 do {
142 if (lang_1.isBlank(instruction.component) || lang_1.isBlank(currentInstruction.component) ||
143 currentInstruction.component.routeName != instruction.component.routeName) {
144 return false;
145 }
146 if (lang_1.isPresent(instruction.component.params)) {
147 collection_1.StringMapWrapper.forEach(instruction.component.params, function (value /** TODO #9100 */, key /** TODO #9100 */) {
148 if (currentInstruction.component.params[key] !== value) {
149 reason = false;
150 }
151 });
152 }
153 currentInstruction = currentInstruction.child;
154 instruction = instruction.child;
155 } while (lang_1.isPresent(currentInstruction) && lang_1.isPresent(instruction) &&
156 !(instruction instanceof instruction_1.DefaultInstruction) && reason);
157 // ignore DefaultInstruction
158 return reason && (lang_1.isBlank(instruction) || instruction instanceof instruction_1.DefaultInstruction);
159 };
160 /**
161 * Dynamically update the routing configuration and trigger a navigation.
162 *
163 * ### Usage
164 *
165 * ```
166 * router.config([
167 * { 'path': '/', 'component': IndexComp },
168 * { 'path': '/user/:id', 'component': UserComp },
169 * ]);
170 * ```
171 */
172 Router.prototype.config = function (definitions) {
173 var _this = this;
174 definitions.forEach(function (routeDefinition) { _this.registry.config(_this.hostComponent, routeDefinition); });
175 return this.renavigate();
176 };
177 /**
178 * Navigate based on the provided Route Link DSL. It's preferred to navigate with this method
179 * over `navigateByUrl`.
180 *
181 * ### Usage
182 *
183 * This method takes an array representing the Route Link DSL:
184 * ```
185 * ['./MyCmp', {param: 3}]
186 * ```
187 * See the {@link RouterLink} directive for more.
188 */
189 Router.prototype.navigate = function (linkParams) {
190 var instruction = this.generate(linkParams);
191 return this.navigateByInstruction(instruction, false);
192 };
193 /**
194 * Navigate to a URL. Returns a promise that resolves when navigation is complete.
195 * It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle.
196 *
197 * If the given URL begins with a `/`, router will navigate absolutely.
198 * If the given URL does not begin with `/`, the router will navigate relative to this component.
199 */
200 Router.prototype.navigateByUrl = function (url, _skipLocationChange) {
201 var _this = this;
202 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
203 return this._currentNavigation = this._currentNavigation.then(function (_) {
204 _this.lastNavigationAttempt = url;
205 _this._startNavigating();
206 return _this._afterPromiseFinishNavigating(_this.recognize(url).then(function (instruction) {
207 if (lang_1.isBlank(instruction)) {
208 return false;
209 }
210 return _this._navigate(instruction, _skipLocationChange);
211 }));
212 });
213 };
214 /**
215 * Navigate via the provided instruction. Returns a promise that resolves when navigation is
216 * complete.
217 */
218 Router.prototype.navigateByInstruction = function (instruction, _skipLocationChange) {
219 var _this = this;
220 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
221 if (lang_1.isBlank(instruction)) {
222 return _resolveToFalse;
223 }
224 return this._currentNavigation = this._currentNavigation.then(function (_) {
225 _this._startNavigating();
226 return _this._afterPromiseFinishNavigating(_this._navigate(instruction, _skipLocationChange));
227 });
228 };
229 /** @internal */
230 Router.prototype._settleInstruction = function (instruction) {
231 var _this = this;
232 return instruction.resolveComponent().then(function (_) {
233 var unsettledInstructions = [];
234 if (lang_1.isPresent(instruction.component)) {
235 instruction.component.reuse = false;
236 }
237 if (lang_1.isPresent(instruction.child)) {
238 unsettledInstructions.push(_this._settleInstruction(instruction.child));
239 }
240 collection_1.StringMapWrapper.forEach(instruction.auxInstruction, function (instruction, _ /** TODO #9100 */) {
241 unsettledInstructions.push(_this._settleInstruction(instruction));
242 });
243 return async_1.PromiseWrapper.all(unsettledInstructions);
244 });
245 };
246 /** @internal */
247 Router.prototype._navigate = function (instruction, _skipLocationChange) {
248 var _this = this;
249 return this._settleInstruction(instruction)
250 .then(function (_) { return _this._routerCanReuse(instruction); })
251 .then(function (_) { return _this._canActivate(instruction); })
252 .then(function (result) {
253 if (!result) {
254 return false;
255 }
256 return _this._routerCanDeactivate(instruction).then(function (result) {
257 if (result) {
258 return _this.commit(instruction, _skipLocationChange).then(function (_) {
259 _this._emitNavigationFinish(instruction.component);
260 return true;
261 });
262 }
263 });
264 });
265 };
266 Router.prototype._emitNavigationFinish = function (instruction) {
267 async_1.ObservableWrapper.callEmit(this._subject, { status: 'success', instruction: instruction });
268 };
269 /** @internal */
270 Router.prototype._emitNavigationFail = function (url) {
271 async_1.ObservableWrapper.callEmit(this._subject, { status: 'fail', url: url });
272 };
273 Router.prototype._afterPromiseFinishNavigating = function (promise) {
274 var _this = this;
275 return async_1.PromiseWrapper.catchError(promise.then(function (_) { return _this._finishNavigating(); }), function (err) {
276 _this._finishNavigating();
277 throw err;
278 });
279 };
280 /*
281 * Recursively set reuse flags
282 */
283 /** @internal */
284 Router.prototype._routerCanReuse = function (instruction) {
285 var _this = this;
286 if (lang_1.isBlank(this._outlet)) {
287 return _resolveToFalse;
288 }
289 if (lang_1.isBlank(instruction.component)) {
290 return _resolveToTrue;
291 }
292 return this._outlet.routerCanReuse(instruction.component).then(function (result) {
293 instruction.component.reuse = result;
294 if (result && lang_1.isPresent(_this._childRouter) && lang_1.isPresent(instruction.child)) {
295 return _this._childRouter._routerCanReuse(instruction.child);
296 }
297 });
298 };
299 Router.prototype._canActivate = function (nextInstruction) {
300 return canActivateOne(nextInstruction, this.currentInstruction);
301 };
302 Router.prototype._routerCanDeactivate = function (instruction) {
303 var _this = this;
304 if (lang_1.isBlank(this._outlet)) {
305 return _resolveToTrue;
306 }
307 var next;
308 var childInstruction = null;
309 var reuse = false;
310 var componentInstruction = null;
311 if (lang_1.isPresent(instruction)) {
312 childInstruction = instruction.child;
313 componentInstruction = instruction.component;
314 reuse = lang_1.isBlank(instruction.component) || instruction.component.reuse;
315 }
316 if (reuse) {
317 next = _resolveToTrue;
318 }
319 else {
320 next = this._outlet.routerCanDeactivate(componentInstruction);
321 }
322 // TODO: aux route lifecycle hooks
323 return next.then(function (result) {
324 if (result == false) {
325 return false;
326 }
327 if (lang_1.isPresent(_this._childRouter)) {
328 // TODO: ideally, this closure would map to async-await in Dart.
329 // For now, casting to any to suppress an error.
330 return _this._childRouter._routerCanDeactivate(childInstruction);
331 }
332 return true;
333 });
334 };
335 /**
336 * Updates this router and all descendant routers according to the given instruction
337 */
338 Router.prototype.commit = function (instruction, _skipLocationChange) {
339 var _this = this;
340 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
341 this.currentInstruction = instruction;
342 var next = _resolveToTrue;
343 if (lang_1.isPresent(this._outlet) && lang_1.isPresent(instruction.component)) {
344 var componentInstruction = instruction.component;
345 if (componentInstruction.reuse) {
346 next = this._outlet.reuse(componentInstruction);
347 }
348 else {
349 next =
350 this.deactivate(instruction).then(function (_) { return _this._outlet.activate(componentInstruction); });
351 }
352 if (lang_1.isPresent(instruction.child)) {
353 next = next.then(function (_) {
354 if (lang_1.isPresent(_this._childRouter)) {
355 return _this._childRouter.commit(instruction.child);
356 }
357 });
358 }
359 }
360 var promises = [];
361 this._auxRouters.forEach(function (router, name) {
362 if (lang_1.isPresent(instruction.auxInstruction[name])) {
363 promises.push(router.commit(instruction.auxInstruction[name]));
364 }
365 });
366 return next.then(function (_) { return async_1.PromiseWrapper.all(promises); });
367 };
368 /** @internal */
369 Router.prototype._startNavigating = function () { this.navigating = true; };
370 /** @internal */
371 Router.prototype._finishNavigating = function () { this.navigating = false; };
372 /**
373 * Subscribe to URL updates from the router
374 */
375 Router.prototype.subscribe = function (onNext, onError) {
376 return async_1.ObservableWrapper.subscribe(this._subject, onNext, onError);
377 };
378 /**
379 * Removes the contents of this router's outlet and all descendant outlets
380 */
381 Router.prototype.deactivate = function (instruction) {
382 var _this = this;
383 var childInstruction = null;
384 var componentInstruction = null;
385 if (lang_1.isPresent(instruction)) {
386 childInstruction = instruction.child;
387 componentInstruction = instruction.component;
388 }
389 var next = _resolveToTrue;
390 if (lang_1.isPresent(this._childRouter)) {
391 next = this._childRouter.deactivate(childInstruction);
392 }
393 if (lang_1.isPresent(this._outlet)) {
394 next = next.then(function (_) { return _this._outlet.deactivate(componentInstruction); });
395 }
396 // TODO: handle aux routes
397 return next;
398 };
399 /**
400 * Given a URL, returns an instruction representing the component graph
401 */
402 Router.prototype.recognize = function (url) {
403 var ancestorComponents = this._getAncestorInstructions();
404 return this.registry.recognize(url, ancestorComponents);
405 };
406 Router.prototype._getAncestorInstructions = function () {
407 var ancestorInstructions = [this.currentInstruction];
408 var ancestorRouter = this;
409 while (lang_1.isPresent(ancestorRouter = ancestorRouter.parent)) {
410 ancestorInstructions.unshift(ancestorRouter.currentInstruction);
411 }
412 return ancestorInstructions;
413 };
414 /**
415 * Navigates to either the last URL successfully navigated to, or the last URL requested if the
416 * router has yet to successfully navigate.
417 */
418 Router.prototype.renavigate = function () {
419 if (lang_1.isBlank(this.lastNavigationAttempt)) {
420 return this._currentNavigation;
421 }
422 return this.navigateByUrl(this.lastNavigationAttempt);
423 };
424 /**
425 * Generate an `Instruction` based on the provided Route Link DSL.
426 */
427 Router.prototype.generate = function (linkParams) {
428 var ancestorInstructions = this._getAncestorInstructions();
429 return this.registry.generate(linkParams, ancestorInstructions);
430 };
431 Router = __decorate([
432 core_1.Injectable(),
433 __metadata('design:paramtypes', [route_registry_1.RouteRegistry, Router, Object, Router])
434 ], Router);
435 return Router;
436}());
437exports.Router = Router;
438var RootRouter = (function (_super) {
439 __extends(RootRouter, _super);
440 function RootRouter(registry, location, primaryComponent) {
441 var _this = this;
442 _super.call(this, registry, null, primaryComponent);
443 this.root = this;
444 this._location = location;
445 this._locationSub = this._location.subscribe(function (change) {
446 // we call recognize ourselves
447 _this.recognize(change['url']).then(function (instruction) {
448 if (lang_1.isPresent(instruction)) {
449 _this.navigateByInstruction(instruction, lang_1.isPresent(change['pop'])).then(function (_) {
450 // this is a popstate event; no need to change the URL
451 if (lang_1.isPresent(change['pop']) && change['type'] != 'hashchange') {
452 return;
453 }
454 var emitPath = instruction.toUrlPath();
455 var emitQuery = instruction.toUrlQuery();
456 if (emitPath.length > 0 && emitPath[0] != '/') {
457 emitPath = '/' + emitPath;
458 }
459 // We've opted to use pushstate and popState APIs regardless of whether you
460 // an app uses HashLocationStrategy or PathLocationStrategy.
461 // However, apps that are migrating might have hash links that operate outside
462 // angular to which routing must respond.
463 // Therefore we know that all hashchange events occur outside Angular.
464 // To support these cases where we respond to hashchanges and redirect as a
465 // result, we need to replace the top item on the stack.
466 if (change['type'] == 'hashchange') {
467 if (instruction.toRootUrl() != _this._location.path()) {
468 _this._location.replaceState(emitPath, emitQuery);
469 }
470 }
471 else {
472 _this._location.go(emitPath, emitQuery);
473 }
474 });
475 }
476 else {
477 _this._emitNavigationFail(change['url']);
478 }
479 });
480 });
481 this.registry.configFromComponent(primaryComponent);
482 this.navigateByUrl(location.path());
483 }
484 RootRouter.prototype.commit = function (instruction, _skipLocationChange) {
485 var _this = this;
486 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
487 var emitPath = instruction.toUrlPath();
488 var emitQuery = instruction.toUrlQuery();
489 if (emitPath.length > 0 && emitPath[0] != '/') {
490 emitPath = '/' + emitPath;
491 }
492 var promise = _super.prototype.commit.call(this, instruction);
493 if (!_skipLocationChange) {
494 if (this._location.isCurrentPathEqualTo(emitPath, emitQuery)) {
495 promise = promise.then(function (_) { _this._location.replaceState(emitPath, emitQuery); });
496 }
497 else {
498 promise = promise.then(function (_) { _this._location.go(emitPath, emitQuery); });
499 }
500 }
501 return promise;
502 };
503 RootRouter.prototype.dispose = function () {
504 if (lang_1.isPresent(this._locationSub)) {
505 async_1.ObservableWrapper.dispose(this._locationSub);
506 this._locationSub = null;
507 }
508 };
509 RootRouter = __decorate([
510 core_1.Injectable(),
511 __param(2, core_1.Inject(route_registry_1.ROUTER_PRIMARY_COMPONENT)),
512 __metadata('design:paramtypes', [route_registry_1.RouteRegistry, common_1.Location, lang_1.Type])
513 ], RootRouter);
514 return RootRouter;
515}(Router));
516exports.RootRouter = RootRouter;
517var ChildRouter = (function (_super) {
518 __extends(ChildRouter, _super);
519 function ChildRouter(parent, hostComponent /** TODO #9100 */) {
520 _super.call(this, parent.registry, parent, hostComponent, parent.root);
521 this.parent = parent;
522 }
523 ChildRouter.prototype.navigateByUrl = function (url, _skipLocationChange) {
524 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
525 // Delegate navigation to the root router
526 return this.parent.navigateByUrl(url, _skipLocationChange);
527 };
528 ChildRouter.prototype.navigateByInstruction = function (instruction, _skipLocationChange) {
529 if (_skipLocationChange === void 0) { _skipLocationChange = false; }
530 // Delegate navigation to the root router
531 return this.parent.navigateByInstruction(instruction, _skipLocationChange);
532 };
533 return ChildRouter;
534}(Router));
535function canActivateOne(nextInstruction, prevInstruction) {
536 var next = _resolveToTrue;
537 if (lang_1.isBlank(nextInstruction.component)) {
538 return next;
539 }
540 if (lang_1.isPresent(nextInstruction.child)) {
541 next = canActivateOne(nextInstruction.child, lang_1.isPresent(prevInstruction) ? prevInstruction.child : null);
542 }
543 return next.then(function (result) {
544 if (result == false) {
545 return false;
546 }
547 if (nextInstruction.component.reuse) {
548 return true;
549 }
550 var hook = route_lifecycle_reflector_1.getCanActivateHook(nextInstruction.component.componentType);
551 if (lang_1.isPresent(hook)) {
552 return hook(nextInstruction.component, lang_1.isPresent(prevInstruction) ? prevInstruction.component : null);
553 }
554 return true;
555 });
556}
557//# sourceMappingURL=router.js.map
\No newline at end of file