UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

501 lines 21.8 kB
'use strict';var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var async_1 = require('angular2/src/facade/async'); var collection_1 = require('angular2/src/facade/collection'); var lang_1 = require('angular2/src/facade/lang'); var exceptions_1 = require('angular2/src/facade/exceptions'); var instruction_1 = require('./instruction'); var route_lifecycle_reflector_1 = require('./route_lifecycle_reflector'); var _resolveToTrue = async_1.PromiseWrapper.resolve(true); var _resolveToFalse = async_1.PromiseWrapper.resolve(false); /** * The `Router` is responsible for mapping URLs to components. * * You can see the state of the router by inspecting the read-only field `router.navigating`. * This may be useful for showing a spinner, for instance. * * ## Concepts * * Routers and component instances have a 1:1 correspondence. * * The router holds reference to a number of {@link RouterOutlet}. * An outlet is a placeholder that the router dynamically fills in depending on the current URL. * * When the router navigates from a URL, it must first recognize it and serialize it into an * `Instruction`. * The router uses the `RouteRegistry` to get an `Instruction`. */ var Router = (function () { function Router(registry, parent, hostComponent) { this.registry = registry; this.parent = parent; this.hostComponent = hostComponent; this.navigating = false; this._currentInstruction = null; this._currentNavigation = _resolveToTrue; this._outlet = null; this._auxRouters = new collection_1.Map(); this._subject = new async_1.EventEmitter(); } /** * Constructs a child router. You probably don't need to use this unless you're writing a reusable * component. */ Router.prototype.childRouter = function (hostComponent) { return this._childRouter = new ChildRouter(this, hostComponent); }; /** * Constructs a child router. You probably don't need to use this unless you're writing a reusable * component. */ Router.prototype.auxRouter = function (hostComponent) { return new ChildRouter(this, hostComponent); }; /** * Register an outlet to notified of primary route changes. * * You probably don't need to use this unless you're writing a reusable component. */ Router.prototype.registerPrimaryOutlet = function (outlet) { if (lang_1.isPresent(outlet.name)) { throw new exceptions_1.BaseException("registerPrimaryOutlet expects to be called with an unnamed outlet."); } this._outlet = outlet; if (lang_1.isPresent(this._currentInstruction)) { return this.commit(this._currentInstruction, false); } return _resolveToTrue; }; /** * Register an outlet to notified of auxiliary route changes. * * You probably don't need to use this unless you're writing a reusable component. */ Router.prototype.registerAuxOutlet = function (outlet) { var outletName = outlet.name; if (lang_1.isBlank(outletName)) { throw new exceptions_1.BaseException("registerAuxOutlet expects to be called with an outlet with a name."); } // TODO... // what is the host of an aux route??? var router = this.auxRouter(this.hostComponent); this._auxRouters.set(outletName, router); router._outlet = outlet; var auxInstruction; if (lang_1.isPresent(this._currentInstruction) && lang_1.isPresent(auxInstruction = this._currentInstruction.auxInstruction[outletName])) { return router.commit(auxInstruction); } return _resolveToTrue; }; /** * Given an instruction, returns `true` if the instruction is currently active, * otherwise `false`. */ Router.prototype.isRouteActive = function (instruction) { var router = this; while (lang_1.isPresent(router.parent) && lang_1.isPresent(instruction.child)) { router = router.parent; instruction = instruction.child; } return lang_1.isPresent(this._currentInstruction) && this._currentInstruction.component == instruction.component; }; /** * Dynamically update the routing configuration and trigger a navigation. * *##Usage * * ``` * router.config([ * { 'path': '/', 'component': IndexComp }, * { 'path': '/user/:id', 'component': UserComp }, * ]); * ``` */ Router.prototype.config = function (definitions) { var _this = this; definitions.forEach(function (routeDefinition) { _this.registry.config(_this.hostComponent, routeDefinition); }); return this.renavigate(); }; /** * Navigate based on the provided Route Link DSL. It's preferred to navigate with this method * over `navigateByUrl`. * *##Usage * * This method takes an array representing the Route Link DSL: * ``` * ['./MyCmp', {param: 3}] * ``` * See the {@link RouterLink} directive for more. */ Router.prototype.navigate = function (linkParams) { var instruction = this.generate(linkParams); return this.navigateByInstruction(instruction, false); }; /** * Navigate to a URL. Returns a promise that resolves when navigation is complete. * It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle. * * If the given URL begins with a `/`, router will navigate absolutely. * If the given URL does not begin with `/`, the router will navigate relative to this component. */ Router.prototype.navigateByUrl = function (url, _skipLocationChange) { var _this = this; if (_skipLocationChange === void 0) { _skipLocationChange = false; } return this._currentNavigation = this._currentNavigation.then(function (_) { _this.lastNavigationAttempt = url; _this._startNavigating(); return _this._afterPromiseFinishNavigating(_this.recognize(url).then(function (instruction) { if (lang_1.isBlank(instruction)) { return false; } return _this._navigate(instruction, _skipLocationChange); })); }); }; /** * Navigate via the provided instruction. Returns a promise that resolves when navigation is * complete. */ Router.prototype.navigateByInstruction = function (instruction, _skipLocationChange) { var _this = this; if (_skipLocationChange === void 0) { _skipLocationChange = false; } if (lang_1.isBlank(instruction)) { return _resolveToFalse; } return this._currentNavigation = this._currentNavigation.then(function (_) { _this._startNavigating(); return _this._afterPromiseFinishNavigating(_this._navigate(instruction, _skipLocationChange)); }); }; /** @internal */ Router.prototype._navigate = function (instruction, _skipLocationChange) { var _this = this; return this._settleInstruction(instruction) .then(function (_) { return _this._canReuse(instruction); }) .then(function (_) { return _this._canActivate(instruction); }) .then(function (result) { if (!result) { return false; } return _this._canDeactivate(instruction) .then(function (result) { if (result) { return _this.commit(instruction, _skipLocationChange) .then(function (_) { _this._emitNavigationFinish(instruction_1.stringifyInstruction(instruction)); return true; }); } }); }); }; // TODO(btford): it'd be nice to remove this method as part of cleaning up the traversal logic // Since refactoring `Router.generate` to return an instruction rather than a string, it's not // guaranteed that the `componentType`s for the terminal async routes have been loaded by the time // we begin navigation. The method below simply traverses instructions and resolves any components // for which `componentType` is not present /** @internal */ Router.prototype._settleInstruction = function (instruction) { var _this = this; var unsettledInstructions = []; if (lang_1.isBlank(instruction.component.componentType)) { unsettledInstructions.push(instruction.component.resolveComponentType().then(function (type) { _this.registry.configFromComponent(type); })); } if (lang_1.isPresent(instruction.child)) { unsettledInstructions.push(this._settleInstruction(instruction.child)); } collection_1.StringMapWrapper.forEach(instruction.auxInstruction, function (instruction, _) { unsettledInstructions.push(_this._settleInstruction(instruction)); }); return async_1.PromiseWrapper.all(unsettledInstructions); }; Router.prototype._emitNavigationFinish = function (url) { async_1.ObservableWrapper.callNext(this._subject, url); }; Router.prototype._afterPromiseFinishNavigating = function (promise) { var _this = this; return async_1.PromiseWrapper.catchError(promise.then(function (_) { return _this._finishNavigating(); }), function (err) { _this._finishNavigating(); throw err; }); }; /* * Recursively set reuse flags */ /** @internal */ Router.prototype._canReuse = function (instruction) { var _this = this; if (lang_1.isBlank(this._outlet)) { return _resolveToFalse; } return this._outlet.canReuse(instruction.component) .then(function (result) { instruction.component.reuse = result; if (result && lang_1.isPresent(_this._childRouter) && lang_1.isPresent(instruction.child)) { return _this._childRouter._canReuse(instruction.child); } }); }; Router.prototype._canActivate = function (nextInstruction) { return canActivateOne(nextInstruction, this._currentInstruction); }; Router.prototype._canDeactivate = function (instruction) { var _this = this; if (lang_1.isBlank(this._outlet)) { return _resolveToTrue; } var next; var childInstruction = null; var reuse = false; var componentInstruction = null; if (lang_1.isPresent(instruction)) { childInstruction = instruction.child; componentInstruction = instruction.component; reuse = instruction.component.reuse; } if (reuse) { next = _resolveToTrue; } else { next = this._outlet.canDeactivate(componentInstruction); } // TODO: aux route lifecycle hooks return next.then(function (result) { if (result == false) { return false; } if (lang_1.isPresent(_this._childRouter)) { return _this._childRouter._canDeactivate(childInstruction); } return true; }); }; /** * Updates this router and all descendant routers according to the given instruction */ Router.prototype.commit = function (instruction, _skipLocationChange) { var _this = this; if (_skipLocationChange === void 0) { _skipLocationChange = false; } this._currentInstruction = instruction; var next = _resolveToTrue; if (lang_1.isPresent(this._outlet)) { var componentInstruction = instruction.component; if (componentInstruction.reuse) { next = this._outlet.reuse(componentInstruction); } else { next = this.deactivate(instruction).then(function (_) { return _this._outlet.activate(componentInstruction); }); } if (lang_1.isPresent(instruction.child)) { next = next.then(function (_) { if (lang_1.isPresent(_this._childRouter)) { return _this._childRouter.commit(instruction.child); } }); } } var promises = []; this._auxRouters.forEach(function (router, name) { if (lang_1.isPresent(instruction.auxInstruction[name])) { promises.push(router.commit(instruction.auxInstruction[name])); } }); return next.then(function (_) { return async_1.PromiseWrapper.all(promises); }); }; /** @internal */ Router.prototype._startNavigating = function () { this.navigating = true; }; /** @internal */ Router.prototype._finishNavigating = function () { this.navigating = false; }; /** * Subscribe to URL updates from the router */ Router.prototype.subscribe = function (onNext) { return async_1.ObservableWrapper.subscribe(this._subject, onNext); }; /** * Removes the contents of this router's outlet and all descendant outlets */ Router.prototype.deactivate = function (instruction) { var _this = this; var childInstruction = null; var componentInstruction = null; if (lang_1.isPresent(instruction)) { childInstruction = instruction.child; componentInstruction = instruction.component; } var next = _resolveToTrue; if (lang_1.isPresent(this._childRouter)) { next = this._childRouter.deactivate(childInstruction); } if (lang_1.isPresent(this._outlet)) { next = next.then(function (_) { return _this._outlet.deactivate(componentInstruction); }); } // TODO: handle aux routes return next; }; /** * Given a URL, returns an instruction representing the component graph */ Router.prototype.recognize = function (url) { return this.registry.recognize(url, this.hostComponent); }; /** * Navigates to either the last URL successfully navigated to, or the last URL requested if the * router has yet to successfully navigate. */ Router.prototype.renavigate = function () { if (lang_1.isBlank(this.lastNavigationAttempt)) { return this._currentNavigation; } return this.navigateByUrl(this.lastNavigationAttempt); }; /** * Generate a URL from a component name and optional map of parameters. The URL is relative to the * app's base href. */ Router.prototype.generate = function (linkParams) { var normalizedLinkParams = splitAndFlattenLinkParams(linkParams); var first = collection_1.ListWrapper.first(normalizedLinkParams); var rest = collection_1.ListWrapper.slice(normalizedLinkParams, 1); var router = this; // The first segment should be either '.' (generate from parent) or '' (generate from root). // When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''. if (first == '') { while (lang_1.isPresent(router.parent)) { router = router.parent; } } else if (first == '..') { router = router.parent; while (collection_1.ListWrapper.first(rest) == '..') { rest = collection_1.ListWrapper.slice(rest, 1); router = router.parent; if (lang_1.isBlank(router)) { throw new exceptions_1.BaseException("Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" has too many \"../\" segments."); } } } else if (first != '.') { // For a link with no leading `./`, `/`, or `../`, we look for a sibling and child. // If both exist, we throw. Otherwise, we prefer whichever exists. var childRouteExists = this.registry.hasRoute(first, this.hostComponent); var parentRouteExists = lang_1.isPresent(this.parent) && this.registry.hasRoute(first, this.parent.hostComponent); if (parentRouteExists && childRouteExists) { var msg = "Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" is ambiguous, use \"./\" or \"../\" to disambiguate."; throw new exceptions_1.BaseException(msg); } if (parentRouteExists) { router = this.parent; } rest = linkParams; } if (rest[rest.length - 1] == '') { rest.pop(); } if (rest.length < 1) { var msg = "Link \"" + collection_1.ListWrapper.toJSON(linkParams) + "\" must include a route name."; throw new exceptions_1.BaseException(msg); } var nextInstruction = this.registry.generate(rest, router.hostComponent); var url = []; var parent = router.parent; while (lang_1.isPresent(parent)) { url.unshift(parent._currentInstruction); parent = parent.parent; } while (url.length > 0) { nextInstruction = url.pop().replaceChild(nextInstruction); } return nextInstruction; }; return Router; })(); exports.Router = Router; var RootRouter = (function (_super) { __extends(RootRouter, _super); function RootRouter(registry, location, primaryComponent) { var _this = this; _super.call(this, registry, null, primaryComponent); this._location = location; this._locationSub = this._location.subscribe(function (change) { return _this.navigateByUrl(change['url'], lang_1.isPresent(change['pop'])); }); this.registry.configFromComponent(primaryComponent); this.navigateByUrl(location.path()); } RootRouter.prototype.commit = function (instruction, _skipLocationChange) { var _this = this; if (_skipLocationChange === void 0) { _skipLocationChange = false; } var emitPath = instruction_1.stringifyInstructionPath(instruction); var emitQuery = instruction_1.stringifyInstructionQuery(instruction); if (emitPath.length > 0) { emitPath = '/' + emitPath; } var promise = _super.prototype.commit.call(this, instruction); if (!_skipLocationChange) { promise = promise.then(function (_) { _this._location.go(emitPath, emitQuery); }); } return promise; }; RootRouter.prototype.dispose = function () { if (lang_1.isPresent(this._locationSub)) { async_1.ObservableWrapper.dispose(this._locationSub); this._locationSub = null; } }; return RootRouter; })(Router); exports.RootRouter = RootRouter; var ChildRouter = (function (_super) { __extends(ChildRouter, _super); function ChildRouter(parent, hostComponent) { _super.call(this, parent.registry, parent, hostComponent); this.parent = parent; } ChildRouter.prototype.navigateByUrl = function (url, _skipLocationChange) { if (_skipLocationChange === void 0) { _skipLocationChange = false; } // Delegate navigation to the root router return this.parent.navigateByUrl(url, _skipLocationChange); }; ChildRouter.prototype.navigateByInstruction = function (instruction, _skipLocationChange) { if (_skipLocationChange === void 0) { _skipLocationChange = false; } // Delegate navigation to the root router return this.parent.navigateByInstruction(instruction, _skipLocationChange); }; return ChildRouter; })(Router); /* * Given: ['/a/b', {c: 2}] * Returns: ['', 'a', 'b', {c: 2}] */ function splitAndFlattenLinkParams(linkParams) { return linkParams.reduce(function (accumulation, item) { if (lang_1.isString(item)) { var strItem = item; return accumulation.concat(strItem.split('/')); } accumulation.push(item); return accumulation; }, []); } function canActivateOne(nextInstruction, prevInstruction) { var next = _resolveToTrue; if (lang_1.isPresent(nextInstruction.child)) { next = canActivateOne(nextInstruction.child, lang_1.isPresent(prevInstruction) ? prevInstruction.child : null); } return next.then(function (result) { if (result == false) { return false; } if (nextInstruction.component.reuse) { return true; } var hook = route_lifecycle_reflector_1.getCanActivateHook(nextInstruction.component.componentType); if (lang_1.isPresent(hook)) { return hook(nextInstruction.component, lang_1.isPresent(prevInstruction) ? prevInstruction.component : null); } return true; }); } //# sourceMappingURL=router.js.map