├── .gitignore ├── LICENSE ├── README.md ├── angular_1_router.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional REPL history 34 | .node_repl_history 35 | .idea 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Excella Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngComponentRouter 2 | > Angular 2 Style Component Router for Angular 1 3 | 4 | This is an extract directly built from Angular 2 source code without modification. Adding this as an npm package, because Angular hasn't released an official version yet. 5 | 6 | ## Quick Start 7 | For a complete example of how to leverage majority of the features of the router, see https://github.com/duluca/angular1.5-starter. Fork it, create issues with it, otherwise just clone it and use it. 8 | 9 | > Note that Angular 1.5.11 is last version of Angular this router and my starter project will work in. However, using this router, Angular Material 1.1.3 and Angular 1.5.11, you can build great production quality applications. The component architecture allows you to follow clean code and SOLID principals, where you can build multi-hundred view apps without your code architecture collapsing under its own weight. Meanwhile you'll be introduced to concepts that form the basis of Angular 2, while leveraging your Angular 1.x skills. If you're looking for a 3-5+ year solution, I highly recommend you start with Angular 2, as that is the foundational framework that'll be supported for Angular v.Next beyond Angular 2. 10 | 11 | For more usage examples of the router and an alternate code architecture, check out https://github.com/brandonroberts/angularjs-component-router. 12 | 13 | ## Based on Angular 2 Commit 14 | 03627aa84d90f7f1d8d62f160997b783fdf9eaa4 15 | 16 | ## Procedure of Extraction 17 | As discussed on https://github.com/angular/angular.js/issues/12926. 18 | 19 | - Install gulp globally `npm install -g gulp@latest` 20 | - Clone the angular 2 repo: `git clone git@github.com:angular/angular.git angular2` 21 | - Go into the angular2 directory and install the dependencies `cd angular2` and `npm install` 22 | - After npm finishes, run the gulp command to build the router file `gulp buildRouter.dev` 23 | - The angular_1_router.js file will be in your angular2/dist folder. 24 | 25 | ## Changes applied 26 | - Replaced unnecessary arrow function on line 3165: config = angular.extend({}, config, { loader: $injector.invoke(loader) }); 27 | 28 | ## Alternatives 29 | This package is out there solely for convenience. You may directly import the router from Angular's npm package. Read more about on https://github.com/angular/angular.js/issues/12926. 30 | -------------------------------------------------------------------------------- /angular_1_router.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | /// 3 | /** 4 | * @name ngOutlet 5 | * 6 | * @description 7 | * An ngOutlet is where resolved content goes. 8 | * 9 | * ## Use 10 | * 11 | * ```html 12 | *
13 | * ``` 14 | * 15 | * The value for the `ngOutlet` attribute is optional. 16 | */ 17 | function ngOutletDirective($animate, $q, $rootRouter) { 18 | var rootRouter = $rootRouter; 19 | return { 20 | restrict: 'AE', 21 | transclude: 'element', 22 | terminal: true, 23 | priority: 400, 24 | require: ['?^^ngOutlet', 'ngOutlet'], 25 | link: outletLink, 26 | controller: (function () { 27 | function class_1() { 28 | } 29 | return class_1; 30 | })(), 31 | controllerAs: '$$ngOutlet' 32 | }; 33 | function outletLink(scope, element, attrs, ctrls, $transclude) { 34 | var Outlet = (function () { 35 | function Outlet(controller, router) { 36 | this.controller = controller; 37 | this.router = router; 38 | } 39 | Outlet.prototype.cleanupLastView = function () { 40 | var _this = this; 41 | if (this.previousLeaveAnimation) { 42 | $animate.cancel(this.previousLeaveAnimation); 43 | this.previousLeaveAnimation = null; 44 | } 45 | if (this.currentScope) { 46 | this.currentScope.$destroy(); 47 | this.currentScope = null; 48 | } 49 | if (this.currentElement) { 50 | this.previousLeaveAnimation = $animate.leave(this.currentElement); 51 | this.previousLeaveAnimation.then(function () { return _this.previousLeaveAnimation = null; }); 52 | this.currentElement = null; 53 | } 54 | }; 55 | Outlet.prototype.reuse = function (instruction) { 56 | var next = $q.when(true); 57 | var previousInstruction = this.currentInstruction; 58 | this.currentInstruction = instruction; 59 | if (this.currentController && this.currentController.$routerOnReuse) { 60 | next = $q.when(this.currentController.$routerOnReuse(this.currentInstruction, previousInstruction)); 61 | } 62 | return next; 63 | }; 64 | Outlet.prototype.routerCanReuse = function (nextInstruction) { 65 | var result; 66 | if (!this.currentInstruction || 67 | this.currentInstruction.componentType !== nextInstruction.componentType) { 68 | result = false; 69 | } 70 | else if (this.currentController && this.currentController.$routerCanReuse) { 71 | result = this.currentController.$routerCanReuse(nextInstruction, this.currentInstruction); 72 | } 73 | else { 74 | result = nextInstruction === this.currentInstruction || 75 | angular.equals(nextInstruction.params, this.currentInstruction.params); 76 | } 77 | return $q.when(result); 78 | }; 79 | Outlet.prototype.routerCanDeactivate = function (instruction) { 80 | if (this.currentController && this.currentController.$routerCanDeactivate) { 81 | return $q.when(this.currentController.$routerCanDeactivate(instruction, this.currentInstruction)); 82 | } 83 | return $q.when(true); 84 | }; 85 | Outlet.prototype.deactivate = function (instruction) { 86 | if (this.currentController && this.currentController.$routerOnDeactivate) { 87 | return $q.when(this.currentController.$routerOnDeactivate(instruction, this.currentInstruction)); 88 | } 89 | return $q.when(); 90 | }; 91 | Outlet.prototype.activate = function (instruction) { 92 | var _this = this; 93 | this.previousInstruction = this.currentInstruction; 94 | this.currentInstruction = instruction; 95 | var componentName = this.controller.$$componentName = instruction.componentType; 96 | if (typeof componentName !== 'string') { 97 | throw new Error('Component is not a string for ' + instruction.urlPath); 98 | } 99 | this.controller.$$template = '<' + dashCase(componentName) + ' $router="::$$router">'; 101 | this.controller.$$router = this.router.childRouter(instruction.componentType); 102 | this.controller.$$outlet = this; 103 | var newScope = scope.$new(); 104 | newScope.$$router = this.controller.$$router; 105 | this.deferredActivation = $q.defer(); 106 | var clone = $transclude(newScope, function (clone) { 107 | $animate.enter(clone, null, _this.currentElement || element); 108 | _this.cleanupLastView(); 109 | }); 110 | this.currentElement = clone; 111 | this.currentScope = newScope; 112 | return this.deferredActivation.promise; 113 | }; 114 | return Outlet; 115 | })(); 116 | var parentCtrl = ctrls[0], myCtrl = ctrls[1], router = (parentCtrl && parentCtrl.$$router) || rootRouter; 117 | myCtrl.$$currentComponent = null; 118 | router.registerPrimaryOutlet(new Outlet(myCtrl, router)); 119 | } 120 | } 121 | /** 122 | * This directive is responsible for compiling the contents of ng-outlet 123 | */ 124 | function ngOutletFillContentDirective($compile) { 125 | return { 126 | restrict: 'EA', 127 | priority: -400, 128 | require: 'ngOutlet', 129 | link: function (scope, element, attrs, ctrl) { 130 | var template = ctrl.$$template; 131 | element.html(template); 132 | $compile(element.contents())(scope); 133 | } 134 | }; 135 | } 136 | function routerTriggerDirective($q) { 137 | return { 138 | require: '^ngOutlet', 139 | priority: -1000, 140 | link: function (scope, element, attr, ngOutletCtrl) { 141 | var promise = $q.when(); 142 | var outlet = ngOutletCtrl.$$outlet; 143 | var currentComponent = outlet.currentController = 144 | element.controller(ngOutletCtrl.$$componentName); 145 | if (currentComponent.$routerOnActivate) { 146 | promise = $q.when(currentComponent.$routerOnActivate(outlet.currentInstruction, outlet.previousInstruction)); 147 | } 148 | promise.then(outlet.deferredActivation.resolve, outlet.deferredActivation.reject); 149 | } 150 | }; 151 | } 152 | /** 153 | * @name ngLink 154 | * @description 155 | * Lets you link to different parts of the app, and automatically generates hrefs. 156 | * 157 | * ## Use 158 | * The directive uses a simple syntax: `ng-link="componentName({ param: paramValue })"` 159 | * 160 | * ### Example 161 | * 162 | * ```js 163 | * angular.module('myApp', ['ngComponentRouter']) 164 | * .controller('AppController', ['$rootRouter', function($rootRouter) { 165 | * $rootRouter.config({ path: '/user/:id', component: 'user' }); 166 | * this.user = { name: 'Brian', id: 123 }; 167 | * }); 168 | * ``` 169 | * 170 | * ```html 171 | *
172 | * {{app.user.name}} 173 | *
174 | * ``` 175 | */ 176 | function ngLinkDirective($rootRouter, $parse) { 177 | return { require: '?^^ngOutlet', restrict: 'A', link: ngLinkDirectiveLinkFn }; 178 | function ngLinkDirectiveLinkFn(scope, element, attrs, ctrl) { 179 | var router = (ctrl && ctrl.$$router) || $rootRouter; 180 | if (!router) { 181 | return; 182 | } 183 | var navigationInstruction = null; 184 | var link = attrs.ngLink || ''; 185 | function getLink(params) { 186 | navigationInstruction = router.generate(params); 187 | scope.$watch(function () { return router.isRouteActive(navigationInstruction); }, function (active) { 188 | if (active) { 189 | element.addClass('ng-link-active'); 190 | } 191 | else { 192 | element.removeClass('ng-link-active'); 193 | } 194 | }); 195 | var navigationHref = navigationInstruction.toLinkUrl(); 196 | return $rootRouter._location.prepareExternalUrl(navigationHref); 197 | } 198 | var routeParamsGetter = $parse(link); 199 | // we can avoid adding a watcher if it's a literal 200 | if (routeParamsGetter.constant) { 201 | var params = routeParamsGetter(); 202 | element.attr('href', getLink(params)); 203 | } 204 | else { 205 | scope.$watch(function () { return routeParamsGetter(scope); }, function (params) { return element.attr('href', getLink(params)); }, true); 206 | } 207 | element.on('click', function (event) { 208 | if (event.which !== 1 || !navigationInstruction) { 209 | return; 210 | } 211 | $rootRouter.navigateByInstruction(navigationInstruction); 212 | event.preventDefault(); 213 | }); 214 | } 215 | } 216 | function dashCase(str) { 217 | return str.replace(/[A-Z]/g, function (match) { return '-' + match.toLowerCase(); }); 218 | } 219 | /* 220 | * A module for adding new a routing system Angular 1. 221 | */ 222 | angular.module('ngComponentRouter', []) 223 | .directive('ngOutlet', ['$animate', '$q', '$rootRouter', ngOutletDirective]) 224 | .directive('ngOutlet', ['$compile', ngOutletFillContentDirective]) 225 | .directive('ngLink', ['$rootRouter', '$parse', ngLinkDirective]) 226 | .directive('$router', ['$q', routerTriggerDirective]); 227 | 228 | angular.module('ngComponentRouter'). 229 | value('$route', null). // can be overloaded with ngRouteShim 230 | // Because Angular 1 has no notion of a root component, we use an object with unique identity 231 | // to represent this. Can be overloaded with a component name 232 | value('$routerRootComponent', new Object()). 233 | 234 | // Unfortunately, $location doesn't expose what the current hashPrefix is 235 | // So we have to monkey patch the $locationProvider to capture this value 236 | provider('$locationHashPrefix', ['$locationProvider', $locationHashPrefixProvider]). 237 | factory('$rootRouter', ['$q', '$location', '$browser', '$rootScope', '$injector', '$routerRootComponent', '$locationHashPrefix', routerFactory]); 238 | 239 | function $locationHashPrefixProvider($locationProvider) { 240 | 241 | // Get hold of the original hashPrefix method 242 | var hashPrefixFn = $locationProvider.hashPrefix.bind($locationProvider); 243 | 244 | // Read the current hashPrefix (in case it was set before this monkey-patch occurred) 245 | var hashPrefix = hashPrefixFn(); 246 | 247 | // Override the helper so that we can read any changes to the prefix (after this monkey-patch) 248 | $locationProvider.hashPrefix = function(prefix) { 249 | if (angular.isDefined(prefix)) { 250 | hashPrefix = prefix; 251 | } 252 | return hashPrefixFn(prefix); 253 | } 254 | 255 | // Return the final hashPrefix as the value of this service 256 | this.$get = function() { return hashPrefix; }; 257 | } 258 | 259 | function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRootComponent, $locationHashPrefix) { 260 | 261 | // When this file is processed, the line below is replaced with 262 | // the contents of `../lib/facades.es5`. 263 | function CONST() { 264 | return (function(target) { 265 | return target; 266 | }); 267 | } 268 | 269 | function CONST_EXPR(expr) { 270 | return expr; 271 | } 272 | 273 | function isPresent (x) { 274 | return !!x; 275 | } 276 | 277 | function isBlank (x) { 278 | return !x; 279 | } 280 | 281 | function isString(obj) { 282 | return typeof obj === 'string'; 283 | } 284 | 285 | function isType (x) { 286 | return typeof x === 'function'; 287 | } 288 | 289 | function isStringMap(obj) { 290 | return typeof obj === 'object' && obj !== null; 291 | } 292 | 293 | function isArray(obj) { 294 | return Array.isArray(obj); 295 | } 296 | 297 | function getTypeNameForDebugging (fn) { 298 | return fn.name || 'Root'; 299 | } 300 | 301 | var PromiseWrapper = { 302 | resolve: function (reason) { 303 | return $q.when(reason); 304 | }, 305 | 306 | reject: function (reason) { 307 | return $q.reject(reason); 308 | }, 309 | 310 | catchError: function (promise, fn) { 311 | return promise.then(null, fn); 312 | }, 313 | all: function (promises) { 314 | return $q.all(promises); 315 | } 316 | }; 317 | 318 | var RegExpWrapper = { 319 | create: function(regExpStr, flags) { 320 | flags = flags ? flags.replace(/g/g, '') : ''; 321 | return new RegExp(regExpStr, flags + 'g'); 322 | }, 323 | firstMatch: function(regExp, input) { 324 | regExp.lastIndex = 0; 325 | return regExp.exec(input); 326 | }, 327 | matcher: function (regExp, input) { 328 | regExp.lastIndex = 0; 329 | return { re: regExp, input: input }; 330 | } 331 | }; 332 | 333 | var reflector = { 334 | annotations: function (fn) { 335 | //TODO: implement me 336 | return fn.annotations || []; 337 | } 338 | }; 339 | 340 | var MapWrapper = { 341 | create: function() { 342 | return new Map(); 343 | }, 344 | 345 | get: function(m, k) { 346 | return m.get(k); 347 | }, 348 | 349 | set: function(m, k, v) { 350 | return m.set(k, v); 351 | }, 352 | 353 | contains: function (m, k) { 354 | return m.has(k); 355 | }, 356 | 357 | forEach: function (m, fn) { 358 | return m.forEach(fn); 359 | } 360 | }; 361 | 362 | var StringMapWrapper = { 363 | create: function () { 364 | return {}; 365 | }, 366 | 367 | set: function (m, k, v) { 368 | return m[k] = v; 369 | }, 370 | 371 | get: function (m, k) { 372 | return m.hasOwnProperty(k) ? m[k] : undefined; 373 | }, 374 | 375 | contains: function (m, k) { 376 | return m.hasOwnProperty(k); 377 | }, 378 | 379 | keys: function(map) { 380 | return Object.keys(map); 381 | }, 382 | 383 | isEmpty: function(map) { 384 | for (var prop in map) { 385 | if (map.hasOwnProperty(prop)) { 386 | return false; 387 | } 388 | } 389 | return true; 390 | }, 391 | 392 | delete: function(map, key) { 393 | delete map[key]; 394 | }, 395 | 396 | forEach: function (m, fn) { 397 | for (var prop in m) { 398 | if (m.hasOwnProperty(prop)) { 399 | fn(m[prop], prop); 400 | } 401 | } 402 | }, 403 | 404 | equals: function (m1, m2) { 405 | var k1 = Object.keys(m1); 406 | var k2 = Object.keys(m2); 407 | if (k1.length != k2.length) { 408 | return false; 409 | } 410 | var key; 411 | for (var i = 0; i < k1.length; i++) { 412 | key = k1[i]; 413 | if (m1[key] !== m2[key]) { 414 | return false; 415 | } 416 | } 417 | return true; 418 | }, 419 | 420 | merge: function(m1, m2) { 421 | var m = {}; 422 | for (var attr in m1) { 423 | if (m1.hasOwnProperty(attr)) { 424 | m[attr] = m1[attr]; 425 | } 426 | } 427 | for (var attr in m2) { 428 | if (m2.hasOwnProperty(attr)) { 429 | m[attr] = m2[attr]; 430 | } 431 | } 432 | return m; 433 | } 434 | }; 435 | 436 | var List = Array; 437 | var ListWrapper = { 438 | toJSON: function(l) { 439 | return JSON.stringify(l); 440 | }, 441 | 442 | clear: function (l) { 443 | l.length = 0; 444 | }, 445 | 446 | create: function () { 447 | return []; 448 | }, 449 | 450 | push: function (l, v) { 451 | return l.push(v); 452 | }, 453 | 454 | forEach: function (l, fn) { 455 | return l.forEach(fn); 456 | }, 457 | 458 | first: function(array) { 459 | if (!array) 460 | return null; 461 | return array[0]; 462 | }, 463 | 464 | last: function(array) { 465 | return (array && array.length) > 0 ? array[array.length - 1] : null; 466 | }, 467 | 468 | map: function (l, fn) { 469 | return l.map(fn); 470 | }, 471 | 472 | join: function (l, str) { 473 | return l.join(str); 474 | }, 475 | 476 | reduce: function(list, fn, init) { 477 | return list.reduce(fn, init); 478 | }, 479 | 480 | filter: function(array, pred) { 481 | return array.filter(pred); 482 | }, 483 | 484 | concat: function(a, b) { 485 | return a.concat(b); 486 | }, 487 | 488 | slice: function(l) { 489 | var from = arguments[1] !== (void 0) ? arguments[1] : 0; 490 | var to = arguments[2] !== (void 0) ? arguments[2] : null; 491 | return l.slice(from, to === null ? undefined : to); 492 | }, 493 | 494 | maximum: function(list, predicate) { 495 | if (list.length == 0) { 496 | return null; 497 | } 498 | var solution = null; 499 | var maxValue = -Infinity; 500 | for (var index = 0; index < list.length; index++) { 501 | var candidate = list[index]; 502 | if (isBlank(candidate)) { 503 | continue; 504 | } 505 | var candidateValue = predicate(candidate); 506 | if (candidateValue > maxValue) { 507 | solution = candidate; 508 | maxValue = candidateValue; 509 | } 510 | } 511 | return solution; 512 | } 513 | }; 514 | 515 | var StringWrapper = { 516 | charCodeAt: function(s, i) { 517 | return s.charCodeAt(i); 518 | }, 519 | 520 | equals: function (s1, s2) { 521 | return s1 === s2; 522 | }, 523 | 524 | split: function(s, re) { 525 | return s.split(re); 526 | }, 527 | 528 | replaceAll: function(s, from, replace) { 529 | return s.replace(from, replace); 530 | }, 531 | 532 | replaceAllMapped: function(s, from, cb) { 533 | return s.replace(from, function(matches) { 534 | // Remove offset & string from the result array 535 | matches.splice(-2, 2); 536 | // The callback receives match, p1, ..., pn 537 | return cb.apply(null, matches); 538 | }); 539 | }, 540 | 541 | contains: function(s, substr) { 542 | return s.indexOf(substr) != -1; 543 | } 544 | 545 | }; 546 | 547 | //TODO: implement? 548 | // I think it's too heavy to ask 1.x users to bring in Rx for the router... 549 | function EventEmitter() {} 550 | 551 | var BaseException = Error; 552 | 553 | var ObservableWrapper = { 554 | callNext: function(ob, val) { 555 | ob.fn(val); 556 | }, 557 | callEmit: function(ob, val) { 558 | ob.fn(val); 559 | }, 560 | 561 | subscribe: function(ob, fn) { 562 | ob.fn = fn; 563 | } 564 | }; 565 | 566 | // TODO: https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L227-L265 567 | var $__router_47_location__ = { 568 | Location: Location 569 | }; 570 | 571 | function Location(){} 572 | Location.prototype.subscribe = function () { 573 | //TODO: implement 574 | }; 575 | Location.prototype.path = function () { 576 | return $location.url(); 577 | }; 578 | Location.prototype.go = function (path, query) { 579 | return $location.url(path + query); 580 | }; 581 | Location.prototype.prepareExternalUrl = function(url) { 582 | if (url.length > 0 && !url.startsWith('/')) { 583 | url = '/' + url; 584 | } 585 | return $location.$$html5 ? '.' + url : '#' + $locationHashPrefix + url; 586 | }; 587 | 588 | 589 | var exports = { 590 | Injectable: function () {}, 591 | OpaqueToken: function () {}, 592 | Inject: function () {} 593 | }; 594 | var routerRequire = function () {return exports;}; 595 | 596 | // When this file is processed, the line below is replaced with 597 | // the contents of the compiled TypeScript classes. 598 | var TouchMap = (function () { 599 | function TouchMap(map) { 600 | var _this = this; 601 | this.map = {}; 602 | this.keys = {}; 603 | if (isPresent(map)) { 604 | StringMapWrapper.forEach(map, function (value, key) { 605 | _this.map[key] = isPresent(value) ? value.toString() : null; 606 | _this.keys[key] = true; 607 | }); 608 | } 609 | } 610 | TouchMap.prototype.get = function (key) { 611 | StringMapWrapper.delete(this.keys, key); 612 | return this.map[key]; 613 | }; 614 | TouchMap.prototype.getUnused = function () { 615 | var _this = this; 616 | var unused = {}; 617 | var keys = StringMapWrapper.keys(this.keys); 618 | keys.forEach(function (key) { return unused[key] = StringMapWrapper.get(_this.map, key); }); 619 | return unused; 620 | }; 621 | return TouchMap; 622 | })(); 623 | exports.TouchMap = TouchMap; 624 | function normalizeString(obj) { 625 | if (isBlank(obj)) { 626 | return null; 627 | } 628 | else { 629 | return obj.toString(); 630 | } 631 | } 632 | exports.normalizeString = normalizeString; 633 | var __extends = (this && this.__extends) || function (d, b) { 634 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 635 | function __() { this.constructor = d; } 636 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 637 | }; 638 | function convertUrlParamsToArray(urlParams) { 639 | var paramsArray = []; 640 | if (isBlank(urlParams)) { 641 | return []; 642 | } 643 | StringMapWrapper.forEach(urlParams, function (value, key) { paramsArray.push((value === true) ? key : key + '=' + value); }); 644 | return paramsArray; 645 | } 646 | exports.convertUrlParamsToArray = convertUrlParamsToArray; 647 | // Convert an object of url parameters into a string that can be used in an URL 648 | function serializeParams(urlParams, joiner) { 649 | if (joiner === void 0) { joiner = '&'; } 650 | return convertUrlParamsToArray(urlParams).join(joiner); 651 | } 652 | exports.serializeParams = serializeParams; 653 | /** 654 | * This class represents a parsed URL 655 | */ 656 | var Url = (function () { 657 | function Url(path, child, auxiliary, params) { 658 | if (child === void 0) { child = null; } 659 | if (auxiliary === void 0) { auxiliary = CONST_EXPR([]); } 660 | if (params === void 0) { params = CONST_EXPR({}); } 661 | this.path = path; 662 | this.child = child; 663 | this.auxiliary = auxiliary; 664 | this.params = params; 665 | } 666 | Url.prototype.toString = function () { 667 | return this.path + this._matrixParamsToString() + this._auxToString() + this._childString(); 668 | }; 669 | Url.prototype.segmentToString = function () { return this.path + this._matrixParamsToString(); }; 670 | /** @internal */ 671 | Url.prototype._auxToString = function () { 672 | return this.auxiliary.length > 0 ? 673 | ('(' + this.auxiliary.map(function (sibling) { return sibling.toString(); }).join('//') + ')') : 674 | ''; 675 | }; 676 | Url.prototype._matrixParamsToString = function () { 677 | var paramString = serializeParams(this.params, ';'); 678 | if (paramString.length > 0) { 679 | return ';' + paramString; 680 | } 681 | return ''; 682 | }; 683 | /** @internal */ 684 | Url.prototype._childString = function () { return isPresent(this.child) ? ('/' + this.child.toString()) : ''; }; 685 | return Url; 686 | })(); 687 | exports.Url = Url; 688 | var RootUrl = (function (_super) { 689 | __extends(RootUrl, _super); 690 | function RootUrl(path, child, auxiliary, params) { 691 | if (child === void 0) { child = null; } 692 | if (auxiliary === void 0) { auxiliary = CONST_EXPR([]); } 693 | if (params === void 0) { params = null; } 694 | _super.call(this, path, child, auxiliary, params); 695 | } 696 | RootUrl.prototype.toString = function () { 697 | return this.path + this._auxToString() + this._childString() + this._queryParamsToString(); 698 | }; 699 | RootUrl.prototype.segmentToString = function () { return this.path + this._queryParamsToString(); }; 700 | RootUrl.prototype._queryParamsToString = function () { 701 | if (isBlank(this.params)) { 702 | return ''; 703 | } 704 | return '?' + serializeParams(this.params); 705 | }; 706 | return RootUrl; 707 | })(Url); 708 | exports.RootUrl = RootUrl; 709 | function pathSegmentsToUrl(pathSegments) { 710 | var url = new Url(pathSegments[pathSegments.length - 1]); 711 | for (var i = pathSegments.length - 2; i >= 0; i -= 1) { 712 | url = new Url(pathSegments[i], url); 713 | } 714 | return url; 715 | } 716 | exports.pathSegmentsToUrl = pathSegmentsToUrl; 717 | var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&#]+'); 718 | function matchUrlSegment(str) { 719 | var match = RegExpWrapper.firstMatch(SEGMENT_RE, str); 720 | return isPresent(match) ? match[0] : ''; 721 | } 722 | var QUERY_PARAM_VALUE_RE = RegExpWrapper.create('^[^\\(\\)\\?;&#]+'); 723 | function matchUrlQueryParamValue(str) { 724 | var match = RegExpWrapper.firstMatch(QUERY_PARAM_VALUE_RE, str); 725 | return isPresent(match) ? match[0] : ''; 726 | } 727 | var UrlParser = (function () { 728 | function UrlParser() { 729 | } 730 | UrlParser.prototype.peekStartsWith = function (str) { return this._remaining.startsWith(str); }; 731 | UrlParser.prototype.capture = function (str) { 732 | if (!this._remaining.startsWith(str)) { 733 | throw new BaseException("Expected \"" + str + "\"."); 734 | } 735 | this._remaining = this._remaining.substring(str.length); 736 | }; 737 | UrlParser.prototype.parse = function (url) { 738 | this._remaining = url; 739 | if (url == '' || url == '/') { 740 | return new Url(''); 741 | } 742 | return this.parseRoot(); 743 | }; 744 | // segment + (aux segments) + (query params) 745 | UrlParser.prototype.parseRoot = function () { 746 | if (this.peekStartsWith('/')) { 747 | this.capture('/'); 748 | } 749 | var path = matchUrlSegment(this._remaining); 750 | this.capture(path); 751 | var aux = []; 752 | if (this.peekStartsWith('(')) { 753 | aux = this.parseAuxiliaryRoutes(); 754 | } 755 | if (this.peekStartsWith(';')) { 756 | // TODO: should these params just be dropped? 757 | this.parseMatrixParams(); 758 | } 759 | var child = null; 760 | if (this.peekStartsWith('/') && !this.peekStartsWith('//')) { 761 | this.capture('/'); 762 | child = this.parseSegment(); 763 | } 764 | var queryParams = null; 765 | if (this.peekStartsWith('?')) { 766 | queryParams = this.parseQueryParams(); 767 | } 768 | return new RootUrl(path, child, aux, queryParams); 769 | }; 770 | // segment + (matrix params) + (aux segments) 771 | UrlParser.prototype.parseSegment = function () { 772 | if (this._remaining.length == 0) { 773 | return null; 774 | } 775 | if (this.peekStartsWith('/')) { 776 | this.capture('/'); 777 | } 778 | var path = matchUrlSegment(this._remaining); 779 | this.capture(path); 780 | var matrixParams = null; 781 | if (this.peekStartsWith(';')) { 782 | matrixParams = this.parseMatrixParams(); 783 | } 784 | var aux = []; 785 | if (this.peekStartsWith('(')) { 786 | aux = this.parseAuxiliaryRoutes(); 787 | } 788 | var child = null; 789 | if (this.peekStartsWith('/') && !this.peekStartsWith('//')) { 790 | this.capture('/'); 791 | child = this.parseSegment(); 792 | } 793 | return new Url(path, child, aux, matrixParams); 794 | }; 795 | UrlParser.prototype.parseQueryParams = function () { 796 | var params = {}; 797 | this.capture('?'); 798 | this.parseQueryParam(params); 799 | while (this._remaining.length > 0 && this.peekStartsWith('&')) { 800 | this.capture('&'); 801 | this.parseQueryParam(params); 802 | } 803 | return params; 804 | }; 805 | UrlParser.prototype.parseMatrixParams = function () { 806 | var params = {}; 807 | while (this._remaining.length > 0 && this.peekStartsWith(';')) { 808 | this.capture(';'); 809 | this.parseParam(params); 810 | } 811 | return params; 812 | }; 813 | UrlParser.prototype.parseParam = function (params) { 814 | var key = matchUrlSegment(this._remaining); 815 | if (isBlank(key)) { 816 | return; 817 | } 818 | this.capture(key); 819 | var value = true; 820 | if (this.peekStartsWith('=')) { 821 | this.capture('='); 822 | var valueMatch = matchUrlSegment(this._remaining); 823 | if (isPresent(valueMatch)) { 824 | value = valueMatch; 825 | this.capture(value); 826 | } 827 | } 828 | params[key] = value; 829 | }; 830 | UrlParser.prototype.parseQueryParam = function (params) { 831 | var key = matchUrlSegment(this._remaining); 832 | if (isBlank(key)) { 833 | return; 834 | } 835 | this.capture(key); 836 | var value = true; 837 | if (this.peekStartsWith('=')) { 838 | this.capture('='); 839 | var valueMatch = matchUrlQueryParamValue(this._remaining); 840 | if (isPresent(valueMatch)) { 841 | value = valueMatch; 842 | this.capture(value); 843 | } 844 | } 845 | params[key] = value; 846 | }; 847 | UrlParser.prototype.parseAuxiliaryRoutes = function () { 848 | var routes = []; 849 | this.capture('('); 850 | while (!this.peekStartsWith(')') && this._remaining.length > 0) { 851 | routes.push(this.parseSegment()); 852 | if (this.peekStartsWith('//')) { 853 | this.capture('//'); 854 | } 855 | } 856 | this.capture(')'); 857 | return routes; 858 | }; 859 | return UrlParser; 860 | })(); 861 | exports.UrlParser = UrlParser; 862 | exports.parser = new UrlParser(); 863 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 864 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 865 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 866 | 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; 867 | return c > 3 && r && Object.defineProperty(target, key, r), r; 868 | }; 869 | var RouteLifecycleHook = (function () { 870 | function RouteLifecycleHook(name) { 871 | this.name = name; 872 | } 873 | RouteLifecycleHook = __decorate([ 874 | CONST() 875 | ], RouteLifecycleHook); 876 | return RouteLifecycleHook; 877 | })(); 878 | exports.RouteLifecycleHook = RouteLifecycleHook; 879 | var CanActivate = (function () { 880 | function CanActivate(fn) { 881 | this.fn = fn; 882 | } 883 | CanActivate = __decorate([ 884 | CONST() 885 | ], CanActivate); 886 | return CanActivate; 887 | })(); 888 | exports.CanActivate = CanActivate; 889 | exports.routerCanReuse = CONST_EXPR(new RouteLifecycleHook('routerCanReuse')); 890 | exports.routerCanDeactivate = CONST_EXPR(new RouteLifecycleHook('routerCanDeactivate')); 891 | exports.routerOnActivate = CONST_EXPR(new RouteLifecycleHook('routerOnActivate')); 892 | exports.routerOnReuse = CONST_EXPR(new RouteLifecycleHook('routerOnReuse')); 893 | exports.routerOnDeactivate = CONST_EXPR(new RouteLifecycleHook('routerOnDeactivate')); 894 | var lifecycle_annotations_impl_1 = routerRequire('./lifecycle_annotations_impl'); 895 | function hasLifecycleHook(e, type) { 896 | if (!(type instanceof Type)) 897 | return false; 898 | return e.name in type.prototype; 899 | } 900 | exports.hasLifecycleHook = hasLifecycleHook; 901 | function getCanActivateHook(type) { 902 | var annotations = reflector.annotations(type); 903 | for (var i = 0; i < annotations.length; i += 1) { 904 | var annotation = annotations[i]; 905 | if (annotation instanceof lifecycle_annotations_impl_1.CanActivate) { 906 | return annotation.fn; 907 | } 908 | } 909 | return null; 910 | } 911 | exports.getCanActivateHook = getCanActivateHook; 912 | var __extends = (this && this.__extends) || function (d, b) { 913 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 914 | function __() { this.constructor = d; } 915 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 916 | }; 917 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 918 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 919 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 920 | 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; 921 | return c > 3 && r && Object.defineProperty(target, key, r), r; 922 | }; 923 | var route_definition_1 = routerRequire('../route_definition'); 924 | exports.RouteDefinition = route_definition_1.RouteDefinition; 925 | var __make_dart_analyzer_happy = null; 926 | /** 927 | * The `RouteConfig` decorator defines routes for a given component. 928 | * 929 | * It takes an array of {@link RouteDefinition}s. 930 | */ 931 | var RouteConfig = (function () { 932 | function RouteConfig(configs) { 933 | this.configs = configs; 934 | } 935 | RouteConfig = __decorate([ 936 | CONST() 937 | ], RouteConfig); 938 | return RouteConfig; 939 | })(); 940 | exports.RouteConfig = RouteConfig; 941 | var AbstractRoute = (function () { 942 | function AbstractRoute(_a) { 943 | var name = _a.name, useAsDefault = _a.useAsDefault, path = _a.path, regex = _a.regex, serializer = _a.serializer, data = _a.data; 944 | this.name = name; 945 | this.useAsDefault = useAsDefault; 946 | this.path = path; 947 | this.regex = regex; 948 | this.serializer = serializer; 949 | this.data = data; 950 | } 951 | AbstractRoute = __decorate([ 952 | CONST() 953 | ], AbstractRoute); 954 | return AbstractRoute; 955 | })(); 956 | exports.AbstractRoute = AbstractRoute; 957 | /** 958 | * `Route` is a type of {@link RouteDefinition} used to route a path to a component. 959 | * 960 | * It has the following properties: 961 | * - `path` is a string that uses the route matcher DSL. 962 | * - `component` a component type. 963 | * - `name` is an optional `CamelCase` string representing the name of the route. 964 | * - `data` is an optional property of any type representing arbitrary route metadata for the given 965 | * route. It is injectable via {@link RouteData}. 966 | * - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child 967 | * route is specified during the navigation. 968 | * 969 | * ### Example 970 | * ``` 971 | * import {RouteConfig, Route} from 'angular2/router'; 972 | * 973 | * @RouteConfig([ 974 | * new Route({path: '/home', component: HomeCmp, name: 'HomeCmp' }) 975 | * ]) 976 | * class MyApp {} 977 | * ``` 978 | */ 979 | var Route = (function (_super) { 980 | __extends(Route, _super); 981 | function Route(_a) { 982 | var name = _a.name, useAsDefault = _a.useAsDefault, path = _a.path, regex = _a.regex, serializer = _a.serializer, data = _a.data, component = _a.component; 983 | _super.call(this, { 984 | name: name, 985 | useAsDefault: useAsDefault, 986 | path: path, 987 | regex: regex, 988 | serializer: serializer, 989 | data: data 990 | }); 991 | this.aux = null; 992 | this.component = component; 993 | } 994 | Route = __decorate([ 995 | CONST() 996 | ], Route); 997 | return Route; 998 | })(AbstractRoute); 999 | exports.Route = Route; 1000 | /** 1001 | * `AuxRoute` is a type of {@link RouteDefinition} used to define an auxiliary route. 1002 | * 1003 | * It takes an object with the following properties: 1004 | * - `path` is a string that uses the route matcher DSL. 1005 | * - `component` a component type. 1006 | * - `name` is an optional `CamelCase` string representing the name of the route. 1007 | * - `data` is an optional property of any type representing arbitrary route metadata for the given 1008 | * route. It is injectable via {@link RouteData}. 1009 | * 1010 | * ### Example 1011 | * ``` 1012 | * import {RouteConfig, AuxRoute} from 'angular2/router'; 1013 | * 1014 | * @RouteConfig([ 1015 | * new AuxRoute({path: '/home', component: HomeCmp}) 1016 | * ]) 1017 | * class MyApp {} 1018 | * ``` 1019 | */ 1020 | var AuxRoute = (function (_super) { 1021 | __extends(AuxRoute, _super); 1022 | function AuxRoute(_a) { 1023 | var name = _a.name, useAsDefault = _a.useAsDefault, path = _a.path, regex = _a.regex, serializer = _a.serializer, data = _a.data, component = _a.component; 1024 | _super.call(this, { 1025 | name: name, 1026 | useAsDefault: useAsDefault, 1027 | path: path, 1028 | regex: regex, 1029 | serializer: serializer, 1030 | data: data 1031 | }); 1032 | this.component = component; 1033 | } 1034 | AuxRoute = __decorate([ 1035 | CONST() 1036 | ], AuxRoute); 1037 | return AuxRoute; 1038 | })(AbstractRoute); 1039 | exports.AuxRoute = AuxRoute; 1040 | /** 1041 | * `AsyncRoute` is a type of {@link RouteDefinition} used to route a path to an asynchronously 1042 | * loaded component. 1043 | * 1044 | * It has the following properties: 1045 | * - `path` is a string that uses the route matcher DSL. 1046 | * - `loader` is a function that returns a promise that resolves to a component. 1047 | * - `name` is an optional `CamelCase` string representing the name of the route. 1048 | * - `data` is an optional property of any type representing arbitrary route metadata for the given 1049 | * route. It is injectable via {@link RouteData}. 1050 | * - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child 1051 | * route is specified during the navigation. 1052 | * 1053 | * ### Example 1054 | * ``` 1055 | * import {RouteConfig, AsyncRoute} from 'angular2/router'; 1056 | * 1057 | * @RouteConfig([ 1058 | * new AsyncRoute({path: '/home', loader: () => Promise.resolve(MyLoadedCmp), name: 1059 | * 'MyLoadedCmp'}) 1060 | * ]) 1061 | * class MyApp {} 1062 | * ``` 1063 | */ 1064 | var AsyncRoute = (function (_super) { 1065 | __extends(AsyncRoute, _super); 1066 | function AsyncRoute(_a) { 1067 | var name = _a.name, useAsDefault = _a.useAsDefault, path = _a.path, regex = _a.regex, serializer = _a.serializer, data = _a.data, loader = _a.loader; 1068 | _super.call(this, { 1069 | name: name, 1070 | useAsDefault: useAsDefault, 1071 | path: path, 1072 | regex: regex, 1073 | serializer: serializer, 1074 | data: data 1075 | }); 1076 | this.aux = null; 1077 | this.loader = loader; 1078 | } 1079 | AsyncRoute = __decorate([ 1080 | CONST() 1081 | ], AsyncRoute); 1082 | return AsyncRoute; 1083 | })(AbstractRoute); 1084 | exports.AsyncRoute = AsyncRoute; 1085 | /** 1086 | * `Redirect` is a type of {@link RouteDefinition} used to route a path to a canonical route. 1087 | * 1088 | * It has the following properties: 1089 | * - `path` is a string that uses the route matcher DSL. 1090 | * - `redirectTo` is an array representing the link DSL. 1091 | * 1092 | * Note that redirects **do not** affect how links are generated. For that, see the `useAsDefault` 1093 | * option. 1094 | * 1095 | * ### Example 1096 | * ``` 1097 | * import {RouteConfig, Route, Redirect} from 'angular2/router'; 1098 | * 1099 | * @RouteConfig([ 1100 | * new Redirect({path: '/', redirectTo: ['/Home'] }), 1101 | * new Route({path: '/home', component: HomeCmp, name: 'Home'}) 1102 | * ]) 1103 | * class MyApp {} 1104 | * ``` 1105 | */ 1106 | var Redirect = (function (_super) { 1107 | __extends(Redirect, _super); 1108 | function Redirect(_a) { 1109 | var name = _a.name, useAsDefault = _a.useAsDefault, path = _a.path, regex = _a.regex, serializer = _a.serializer, data = _a.data, redirectTo = _a.redirectTo; 1110 | _super.call(this, { 1111 | name: name, 1112 | useAsDefault: useAsDefault, 1113 | path: path, 1114 | regex: regex, 1115 | serializer: serializer, 1116 | data: data 1117 | }); 1118 | this.redirectTo = redirectTo; 1119 | } 1120 | Redirect = __decorate([ 1121 | CONST() 1122 | ], Redirect); 1123 | return Redirect; 1124 | })(AbstractRoute); 1125 | exports.Redirect = Redirect; 1126 | var route_config_decorator_1 = routerRequire('./route_config_decorator'); 1127 | /** 1128 | * Given a JS Object that represents a route config, returns a corresponding Route, AsyncRoute, 1129 | * AuxRoute or Redirect object. 1130 | * 1131 | * Also wraps an AsyncRoute's loader function to add the loaded component's route config to the 1132 | * `RouteRegistry`. 1133 | */ 1134 | function normalizeRouteConfig(config, registry) { 1135 | if (config instanceof route_config_decorator_1.AsyncRoute) { 1136 | var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry); 1137 | return new route_config_decorator_1.AsyncRoute({ 1138 | path: config.path, 1139 | loader: wrappedLoader, 1140 | name: config.name, 1141 | data: config.data, 1142 | useAsDefault: config.useAsDefault 1143 | }); 1144 | } 1145 | if (config instanceof route_config_decorator_1.Route || config instanceof route_config_decorator_1.Redirect || config instanceof route_config_decorator_1.AuxRoute) { 1146 | return config; 1147 | } 1148 | if ((+!!config.component) + (+!!config.redirectTo) + (+!!config.loader) != 1) { 1149 | throw new BaseException("Route config should contain exactly one \"component\", \"loader\", or \"redirectTo\" property."); 1150 | } 1151 | if (config.as && config.name) { 1152 | throw new BaseException("Route config should contain exactly one \"as\" or \"name\" property."); 1153 | } 1154 | if (config.as) { 1155 | config.name = config.as; 1156 | } 1157 | if (config.loader) { 1158 | var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry); 1159 | return new route_config_decorator_1.AsyncRoute({ 1160 | path: config.path, 1161 | loader: wrappedLoader, 1162 | name: config.name, 1163 | data: config.data, 1164 | useAsDefault: config.useAsDefault 1165 | }); 1166 | } 1167 | if (config.aux) { 1168 | return new route_config_decorator_1.AuxRoute({ path: config.aux, component: config.component, name: config.name }); 1169 | } 1170 | if (config.component) { 1171 | if (typeof config.component == 'object') { 1172 | var componentDefinitionObject = config.component; 1173 | if (componentDefinitionObject.type == 'constructor') { 1174 | return new route_config_decorator_1.Route({ 1175 | path: config.path, 1176 | component: componentDefinitionObject.constructor, 1177 | name: config.name, 1178 | data: config.data, 1179 | useAsDefault: config.useAsDefault 1180 | }); 1181 | } 1182 | else if (componentDefinitionObject.type == 'loader') { 1183 | return new route_config_decorator_1.AsyncRoute({ 1184 | path: config.path, 1185 | loader: componentDefinitionObject.loader, 1186 | name: config.name, 1187 | data: config.data, 1188 | useAsDefault: config.useAsDefault 1189 | }); 1190 | } 1191 | else { 1192 | throw new BaseException("Invalid component type \"" + componentDefinitionObject.type + "\". Valid types are \"constructor\" and \"loader\"."); 1193 | } 1194 | } 1195 | return new route_config_decorator_1.Route(config); 1196 | } 1197 | if (config.redirectTo) { 1198 | return new route_config_decorator_1.Redirect({ path: config.path, redirectTo: config.redirectTo }); 1199 | } 1200 | return config; 1201 | } 1202 | exports.normalizeRouteConfig = normalizeRouteConfig; 1203 | function wrapLoaderToReconfigureRegistry(loader, registry) { 1204 | return function () { 1205 | return loader().then(function (componentType) { 1206 | registry.configFromComponent(componentType); 1207 | return componentType; 1208 | }); 1209 | }; 1210 | } 1211 | function assertComponentExists(component, path) { 1212 | if (!isType(component)) { 1213 | throw new BaseException("Component for route \"" + path + "\" is not defined, or is not a class."); 1214 | } 1215 | } 1216 | exports.assertComponentExists = assertComponentExists; 1217 | var instruction_1 = routerRequire('../../instruction'); 1218 | var AsyncRouteHandler = (function () { 1219 | function AsyncRouteHandler(_loader, data) { 1220 | if (data === void 0) { data = null; } 1221 | this._loader = _loader; 1222 | /** @internal */ 1223 | this._resolvedComponent = null; 1224 | this.data = isPresent(data) ? new instruction_1.RouteData(data) : instruction_1.BLANK_ROUTE_DATA; 1225 | } 1226 | AsyncRouteHandler.prototype.resolveComponentType = function () { 1227 | var _this = this; 1228 | if (isPresent(this._resolvedComponent)) { 1229 | return this._resolvedComponent; 1230 | } 1231 | return this._resolvedComponent = this._loader().then(function (componentType) { 1232 | _this.componentType = componentType; 1233 | return componentType; 1234 | }); 1235 | }; 1236 | return AsyncRouteHandler; 1237 | })(); 1238 | exports.AsyncRouteHandler = AsyncRouteHandler; 1239 | var instruction_1 = routerRequire('../../instruction'); 1240 | var SyncRouteHandler = (function () { 1241 | function SyncRouteHandler(componentType, data) { 1242 | this.componentType = componentType; 1243 | /** @internal */ 1244 | this._resolvedComponent = null; 1245 | this._resolvedComponent = PromiseWrapper.resolve(componentType); 1246 | this.data = isPresent(data) ? new instruction_1.RouteData(data) : instruction_1.BLANK_ROUTE_DATA; 1247 | } 1248 | SyncRouteHandler.prototype.resolveComponentType = function () { return this._resolvedComponent; }; 1249 | return SyncRouteHandler; 1250 | })(); 1251 | exports.SyncRouteHandler = SyncRouteHandler; 1252 | var __extends = (this && this.__extends) || function (d, b) { 1253 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 1254 | function __() { this.constructor = d; } 1255 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 1256 | }; 1257 | var url_parser_1 = routerRequire('../url_parser'); 1258 | var instruction_1 = routerRequire('../instruction'); 1259 | // RouteMatch objects hold information about a match between a rule and a URL 1260 | var RouteMatch = (function () { 1261 | function RouteMatch() { 1262 | } 1263 | return RouteMatch; 1264 | })(); 1265 | exports.RouteMatch = RouteMatch; 1266 | var PathMatch = (function (_super) { 1267 | __extends(PathMatch, _super); 1268 | function PathMatch(instruction, remaining, remainingAux) { 1269 | _super.call(this); 1270 | this.instruction = instruction; 1271 | this.remaining = remaining; 1272 | this.remainingAux = remainingAux; 1273 | } 1274 | return PathMatch; 1275 | })(RouteMatch); 1276 | exports.PathMatch = PathMatch; 1277 | var RedirectMatch = (function (_super) { 1278 | __extends(RedirectMatch, _super); 1279 | function RedirectMatch(redirectTo, specificity) { 1280 | _super.call(this); 1281 | this.redirectTo = redirectTo; 1282 | this.specificity = specificity; 1283 | } 1284 | return RedirectMatch; 1285 | })(RouteMatch); 1286 | exports.RedirectMatch = RedirectMatch; 1287 | var RedirectRule = (function () { 1288 | function RedirectRule(_pathRecognizer, redirectTo) { 1289 | this._pathRecognizer = _pathRecognizer; 1290 | this.redirectTo = redirectTo; 1291 | this.hash = this._pathRecognizer.hash; 1292 | } 1293 | Object.defineProperty(RedirectRule.prototype, "path", { 1294 | get: function () { return this._pathRecognizer.toString(); }, 1295 | set: function (val) { throw new BaseException('you cannot set the path of a RedirectRule directly'); }, 1296 | enumerable: true, 1297 | configurable: true 1298 | }); 1299 | /** 1300 | * Returns `null` or a `ParsedUrl` representing the new path to match 1301 | */ 1302 | RedirectRule.prototype.recognize = function (beginningSegment) { 1303 | var match = null; 1304 | if (isPresent(this._pathRecognizer.matchUrl(beginningSegment))) { 1305 | match = new RedirectMatch(this.redirectTo, this._pathRecognizer.specificity); 1306 | } 1307 | return PromiseWrapper.resolve(match); 1308 | }; 1309 | RedirectRule.prototype.generate = function (params) { 1310 | throw new BaseException("Tried to generate a redirect."); 1311 | }; 1312 | return RedirectRule; 1313 | })(); 1314 | exports.RedirectRule = RedirectRule; 1315 | // represents something like '/foo/:bar' 1316 | var RouteRule = (function () { 1317 | // TODO: cache component instruction instances by params and by ParsedUrl instance 1318 | function RouteRule(_routePath, handler, _routeName) { 1319 | this._routePath = _routePath; 1320 | this.handler = handler; 1321 | this._routeName = _routeName; 1322 | this._cache = new Map(); 1323 | this.specificity = this._routePath.specificity; 1324 | this.hash = this._routePath.hash; 1325 | this.terminal = this._routePath.terminal; 1326 | } 1327 | Object.defineProperty(RouteRule.prototype, "path", { 1328 | get: function () { return this._routePath.toString(); }, 1329 | set: function (val) { throw new BaseException('you cannot set the path of a RouteRule directly'); }, 1330 | enumerable: true, 1331 | configurable: true 1332 | }); 1333 | RouteRule.prototype.recognize = function (beginningSegment) { 1334 | var _this = this; 1335 | var res = this._routePath.matchUrl(beginningSegment); 1336 | if (isBlank(res)) { 1337 | return null; 1338 | } 1339 | return this.handler.resolveComponentType().then(function (_) { 1340 | var componentInstruction = _this._getInstruction(res.urlPath, res.urlParams, res.allParams); 1341 | return new PathMatch(componentInstruction, res.rest, res.auxiliary); 1342 | }); 1343 | }; 1344 | RouteRule.prototype.generate = function (params) { 1345 | var generated = this._routePath.generateUrl(params); 1346 | var urlPath = generated.urlPath; 1347 | var urlParams = generated.urlParams; 1348 | return this._getInstruction(urlPath, url_parser_1.convertUrlParamsToArray(urlParams), params); 1349 | }; 1350 | RouteRule.prototype.generateComponentPathValues = function (params) { 1351 | return this._routePath.generateUrl(params); 1352 | }; 1353 | RouteRule.prototype._getInstruction = function (urlPath, urlParams, params) { 1354 | if (isBlank(this.handler.componentType)) { 1355 | throw new BaseException("Tried to get instruction before the type was loaded."); 1356 | } 1357 | var hashKey = urlPath + '?' + urlParams.join('&'); 1358 | if (this._cache.has(hashKey)) { 1359 | return this._cache.get(hashKey); 1360 | } 1361 | var instruction = new instruction_1.ComponentInstruction(urlPath, urlParams, this.handler.data, this.handler.componentType, this.terminal, this.specificity, params, this._routeName); 1362 | this._cache.set(hashKey, instruction); 1363 | return instruction; 1364 | }; 1365 | return RouteRule; 1366 | })(); 1367 | exports.RouteRule = RouteRule; 1368 | var rules_1 = routerRequire('./rules'); 1369 | var route_config_impl_1 = routerRequire('../route_config/route_config_impl'); 1370 | var async_route_handler_1 = routerRequire('./route_handlers/async_route_handler'); 1371 | var sync_route_handler_1 = routerRequire('./route_handlers/sync_route_handler'); 1372 | var param_route_path_1 = routerRequire('./route_paths/param_route_path'); 1373 | var regex_route_path_1 = routerRequire('./route_paths/regex_route_path'); 1374 | /** 1375 | * A `RuleSet` is responsible for recognizing routes for a particular component. 1376 | * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of 1377 | * components. 1378 | */ 1379 | var RuleSet = (function () { 1380 | function RuleSet() { 1381 | this.rulesByName = new Map(); 1382 | // map from name to rule 1383 | this.auxRulesByName = new Map(); 1384 | // map from starting path to rule 1385 | this.auxRulesByPath = new Map(); 1386 | // TODO: optimize this into a trie 1387 | this.rules = []; 1388 | // the rule to use automatically when recognizing or generating from this rule set 1389 | this.defaultRule = null; 1390 | } 1391 | /** 1392 | * Configure additional rules in this rule set from a route definition 1393 | * @returns {boolean} true if the config is terminal 1394 | */ 1395 | RuleSet.prototype.config = function (config) { 1396 | var handler; 1397 | if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) { 1398 | var suggestedName = config.name[0].toUpperCase() + config.name.substring(1); 1399 | throw new BaseException("Route \"" + config.path + "\" with name \"" + config.name + "\" does not begin with an uppercase letter. Route names should be CamelCase like \"" + suggestedName + "\"."); 1400 | } 1401 | if (config instanceof route_config_impl_1.AuxRoute) { 1402 | handler = new sync_route_handler_1.SyncRouteHandler(config.component, config.data); 1403 | var routePath_1 = this._getRoutePath(config); 1404 | var auxRule = new rules_1.RouteRule(routePath_1, handler, config.name); 1405 | this.auxRulesByPath.set(routePath_1.toString(), auxRule); 1406 | if (isPresent(config.name)) { 1407 | this.auxRulesByName.set(config.name, auxRule); 1408 | } 1409 | return auxRule.terminal; 1410 | } 1411 | var useAsDefault = false; 1412 | if (config instanceof route_config_impl_1.Redirect) { 1413 | var routePath_2 = this._getRoutePath(config); 1414 | var redirector = new rules_1.RedirectRule(routePath_2, config.redirectTo); 1415 | this._assertNoHashCollision(redirector.hash, config.path); 1416 | this.rules.push(redirector); 1417 | return true; 1418 | } 1419 | if (config instanceof route_config_impl_1.Route) { 1420 | handler = new sync_route_handler_1.SyncRouteHandler(config.component, config.data); 1421 | useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault; 1422 | } 1423 | else if (config instanceof route_config_impl_1.AsyncRoute) { 1424 | handler = new async_route_handler_1.AsyncRouteHandler(config.loader, config.data); 1425 | useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault; 1426 | } 1427 | var routePath = this._getRoutePath(config); 1428 | var newRule = new rules_1.RouteRule(routePath, handler, config.name); 1429 | this._assertNoHashCollision(newRule.hash, config.path); 1430 | if (useAsDefault) { 1431 | if (isPresent(this.defaultRule)) { 1432 | throw new BaseException("Only one route can be default"); 1433 | } 1434 | this.defaultRule = newRule; 1435 | } 1436 | this.rules.push(newRule); 1437 | if (isPresent(config.name)) { 1438 | this.rulesByName.set(config.name, newRule); 1439 | } 1440 | return newRule.terminal; 1441 | }; 1442 | /** 1443 | * Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route. 1444 | */ 1445 | RuleSet.prototype.recognize = function (urlParse) { 1446 | var solutions = []; 1447 | this.rules.forEach(function (routeRecognizer) { 1448 | var pathMatch = routeRecognizer.recognize(urlParse); 1449 | if (isPresent(pathMatch)) { 1450 | solutions.push(pathMatch); 1451 | } 1452 | }); 1453 | // handle cases where we are routing just to an aux route 1454 | if (solutions.length == 0 && isPresent(urlParse) && urlParse.auxiliary.length > 0) { 1455 | return [PromiseWrapper.resolve(new rules_1.PathMatch(null, null, urlParse.auxiliary))]; 1456 | } 1457 | return solutions; 1458 | }; 1459 | RuleSet.prototype.recognizeAuxiliary = function (urlParse) { 1460 | var routeRecognizer = this.auxRulesByPath.get(urlParse.path); 1461 | if (isPresent(routeRecognizer)) { 1462 | return [routeRecognizer.recognize(urlParse)]; 1463 | } 1464 | return [PromiseWrapper.resolve(null)]; 1465 | }; 1466 | RuleSet.prototype.hasRoute = function (name) { return this.rulesByName.has(name); }; 1467 | RuleSet.prototype.componentLoaded = function (name) { 1468 | return this.hasRoute(name) && isPresent(this.rulesByName.get(name).handler.componentType); 1469 | }; 1470 | RuleSet.prototype.loadComponent = function (name) { 1471 | return this.rulesByName.get(name).handler.resolveComponentType(); 1472 | }; 1473 | RuleSet.prototype.generate = function (name, params) { 1474 | var rule = this.rulesByName.get(name); 1475 | if (isBlank(rule)) { 1476 | return null; 1477 | } 1478 | return rule.generate(params); 1479 | }; 1480 | RuleSet.prototype.generateAuxiliary = function (name, params) { 1481 | var rule = this.auxRulesByName.get(name); 1482 | if (isBlank(rule)) { 1483 | return null; 1484 | } 1485 | return rule.generate(params); 1486 | }; 1487 | RuleSet.prototype._assertNoHashCollision = function (hash, path) { 1488 | this.rules.forEach(function (rule) { 1489 | if (hash == rule.hash) { 1490 | throw new BaseException("Configuration '" + path + "' conflicts with existing route '" + rule.path + "'"); 1491 | } 1492 | }); 1493 | }; 1494 | RuleSet.prototype._getRoutePath = function (config) { 1495 | if (isPresent(config.regex)) { 1496 | if (isFunction(config.serializer)) { 1497 | return new regex_route_path_1.RegexRoutePath(config.regex, config.serializer); 1498 | } 1499 | else { 1500 | throw new BaseException("Route provides a regex property, '" + config.regex + "', but no serializer property"); 1501 | } 1502 | } 1503 | if (isPresent(config.path)) { 1504 | // Auxiliary routes do not have a slash at the start 1505 | var path = (config instanceof route_config_impl_1.AuxRoute && config.path.startsWith('/')) ? 1506 | config.path.substring(1) : 1507 | config.path; 1508 | return new param_route_path_1.ParamRoutePath(path); 1509 | } 1510 | throw new BaseException('Route must provide either a path or regex property'); 1511 | }; 1512 | return RuleSet; 1513 | })(); 1514 | exports.RuleSet = RuleSet; 1515 | var MatchedUrl = (function () { 1516 | function MatchedUrl(urlPath, urlParams, allParams, auxiliary, rest) { 1517 | this.urlPath = urlPath; 1518 | this.urlParams = urlParams; 1519 | this.allParams = allParams; 1520 | this.auxiliary = auxiliary; 1521 | this.rest = rest; 1522 | } 1523 | return MatchedUrl; 1524 | })(); 1525 | exports.MatchedUrl = MatchedUrl; 1526 | var GeneratedUrl = (function () { 1527 | function GeneratedUrl(urlPath, urlParams) { 1528 | this.urlPath = urlPath; 1529 | this.urlParams = urlParams; 1530 | } 1531 | return GeneratedUrl; 1532 | })(); 1533 | exports.GeneratedUrl = GeneratedUrl; 1534 | var utils_1 = routerRequire('../../utils'); 1535 | var url_parser_1 = routerRequire('../../url_parser'); 1536 | var route_path_1 = routerRequire('./route_path'); 1537 | /** 1538 | * Identified by a `...` URL segment. This indicates that the 1539 | * Route will continue to be matched by child `Router`s. 1540 | */ 1541 | var ContinuationPathSegment = (function () { 1542 | function ContinuationPathSegment() { 1543 | this.name = ''; 1544 | this.specificity = ''; 1545 | this.hash = '...'; 1546 | } 1547 | ContinuationPathSegment.prototype.generate = function (params) { return ''; }; 1548 | ContinuationPathSegment.prototype.match = function (path) { return true; }; 1549 | return ContinuationPathSegment; 1550 | })(); 1551 | /** 1552 | * Identified by a string not starting with a `:` or `*`. 1553 | * Only matches the URL segments that equal the segment path 1554 | */ 1555 | var StaticPathSegment = (function () { 1556 | function StaticPathSegment(path) { 1557 | this.path = path; 1558 | this.name = ''; 1559 | this.specificity = '2'; 1560 | this.hash = path; 1561 | } 1562 | StaticPathSegment.prototype.match = function (path) { return path == this.path; }; 1563 | StaticPathSegment.prototype.generate = function (params) { return this.path; }; 1564 | return StaticPathSegment; 1565 | })(); 1566 | /** 1567 | * Identified by a string starting with `:`. Indicates a segment 1568 | * that can contain a value that will be extracted and provided to 1569 | * a matching `Instruction`. 1570 | */ 1571 | var DynamicPathSegment = (function () { 1572 | function DynamicPathSegment(name) { 1573 | this.name = name; 1574 | this.specificity = '1'; 1575 | this.hash = ':'; 1576 | } 1577 | DynamicPathSegment.prototype.match = function (path) { return path.length > 0; }; 1578 | DynamicPathSegment.prototype.generate = function (params) { 1579 | if (!StringMapWrapper.contains(params.map, this.name)) { 1580 | throw new BaseException("Route generator for '" + this.name + "' was not included in parameters passed."); 1581 | } 1582 | return encodeDynamicSegment(utils_1.normalizeString(params.get(this.name))); 1583 | }; 1584 | DynamicPathSegment.paramMatcher = /^:([^\/]+)$/g; 1585 | return DynamicPathSegment; 1586 | })(); 1587 | /** 1588 | * Identified by a string starting with `*` Indicates that all the following 1589 | * segments match this route and that the value of these segments should 1590 | * be provided to a matching `Instruction`. 1591 | */ 1592 | var StarPathSegment = (function () { 1593 | function StarPathSegment(name) { 1594 | this.name = name; 1595 | this.specificity = '0'; 1596 | this.hash = '*'; 1597 | } 1598 | StarPathSegment.prototype.match = function (path) { return true; }; 1599 | StarPathSegment.prototype.generate = function (params) { return utils_1.normalizeString(params.get(this.name)); }; 1600 | StarPathSegment.wildcardMatcher = /^\*([^\/]+)$/g; 1601 | return StarPathSegment; 1602 | })(); 1603 | /** 1604 | * Parses a URL string using a given matcher DSL, and generates URLs from param maps 1605 | */ 1606 | var ParamRoutePath = (function () { 1607 | /** 1608 | * Takes a string representing the matcher DSL 1609 | */ 1610 | function ParamRoutePath(routePath) { 1611 | this.routePath = routePath; 1612 | this.terminal = true; 1613 | this._assertValidPath(routePath); 1614 | this._parsePathString(routePath); 1615 | this.specificity = this._calculateSpecificity(); 1616 | this.hash = this._calculateHash(); 1617 | var lastSegment = this._segments[this._segments.length - 1]; 1618 | this.terminal = !(lastSegment instanceof ContinuationPathSegment); 1619 | } 1620 | ParamRoutePath.prototype.matchUrl = function (url) { 1621 | var nextUrlSegment = url; 1622 | var currentUrlSegment; 1623 | var positionalParams = {}; 1624 | var captured = []; 1625 | for (var i = 0; i < this._segments.length; i += 1) { 1626 | var pathSegment = this._segments[i]; 1627 | currentUrlSegment = nextUrlSegment; 1628 | if (pathSegment instanceof ContinuationPathSegment) { 1629 | break; 1630 | } 1631 | if (isPresent(currentUrlSegment)) { 1632 | // the star segment consumes all of the remaining URL, including matrix params 1633 | if (pathSegment instanceof StarPathSegment) { 1634 | positionalParams[pathSegment.name] = currentUrlSegment.toString(); 1635 | captured.push(currentUrlSegment.toString()); 1636 | nextUrlSegment = null; 1637 | break; 1638 | } 1639 | captured.push(currentUrlSegment.path); 1640 | if (pathSegment instanceof DynamicPathSegment) { 1641 | positionalParams[pathSegment.name] = decodeDynamicSegment(currentUrlSegment.path); 1642 | } 1643 | else if (!pathSegment.match(currentUrlSegment.path)) { 1644 | return null; 1645 | } 1646 | nextUrlSegment = currentUrlSegment.child; 1647 | } 1648 | else if (!pathSegment.match('')) { 1649 | return null; 1650 | } 1651 | } 1652 | if (this.terminal && isPresent(nextUrlSegment)) { 1653 | return null; 1654 | } 1655 | var urlPath = captured.join('/'); 1656 | var auxiliary = []; 1657 | var urlParams = []; 1658 | var allParams = positionalParams; 1659 | if (isPresent(currentUrlSegment)) { 1660 | // If this is the root component, read query params. Otherwise, read matrix params. 1661 | var paramsSegment = url instanceof url_parser_1.RootUrl ? url : currentUrlSegment; 1662 | if (isPresent(paramsSegment.params)) { 1663 | allParams = StringMapWrapper.merge(paramsSegment.params, positionalParams); 1664 | urlParams = url_parser_1.convertUrlParamsToArray(paramsSegment.params); 1665 | } 1666 | else { 1667 | allParams = positionalParams; 1668 | } 1669 | auxiliary = currentUrlSegment.auxiliary; 1670 | } 1671 | return new route_path_1.MatchedUrl(urlPath, urlParams, allParams, auxiliary, nextUrlSegment); 1672 | }; 1673 | ParamRoutePath.prototype.generateUrl = function (params) { 1674 | var paramTokens = new utils_1.TouchMap(params); 1675 | var path = []; 1676 | for (var i = 0; i < this._segments.length; i++) { 1677 | var segment = this._segments[i]; 1678 | if (!(segment instanceof ContinuationPathSegment)) { 1679 | path.push(segment.generate(paramTokens)); 1680 | } 1681 | } 1682 | var urlPath = path.join('/'); 1683 | var nonPositionalParams = paramTokens.getUnused(); 1684 | var urlParams = nonPositionalParams; 1685 | return new route_path_1.GeneratedUrl(urlPath, urlParams); 1686 | }; 1687 | ParamRoutePath.prototype.toString = function () { return this.routePath; }; 1688 | ParamRoutePath.prototype._parsePathString = function (routePath) { 1689 | // normalize route as not starting with a "/". Recognition will 1690 | // also normalize. 1691 | if (routePath.startsWith('/')) { 1692 | routePath = routePath.substring(1); 1693 | } 1694 | var segmentStrings = routePath.split('/'); 1695 | this._segments = []; 1696 | var limit = segmentStrings.length - 1; 1697 | for (var i = 0; i <= limit; i++) { 1698 | var segment = segmentStrings[i], match; 1699 | if (isPresent(match = RegExpWrapper.firstMatch(DynamicPathSegment.paramMatcher, segment))) { 1700 | this._segments.push(new DynamicPathSegment(match[1])); 1701 | } 1702 | else if (isPresent(match = RegExpWrapper.firstMatch(StarPathSegment.wildcardMatcher, segment))) { 1703 | this._segments.push(new StarPathSegment(match[1])); 1704 | } 1705 | else if (segment == '...') { 1706 | if (i < limit) { 1707 | throw new BaseException("Unexpected \"...\" before the end of the path for \"" + routePath + "\"."); 1708 | } 1709 | this._segments.push(new ContinuationPathSegment()); 1710 | } 1711 | else { 1712 | this._segments.push(new StaticPathSegment(segment)); 1713 | } 1714 | } 1715 | }; 1716 | ParamRoutePath.prototype._calculateSpecificity = function () { 1717 | // The "specificity" of a path is used to determine which route is used when multiple routes 1718 | // match 1719 | // a URL. Static segments (like "/foo") are the most specific, followed by dynamic segments 1720 | // (like 1721 | // "/:id"). Star segments add no specificity. Segments at the start of the path are more 1722 | // specific 1723 | // than proceeding ones. 1724 | // 1725 | // The code below uses place values to combine the different types of segments into a single 1726 | // string that we can sort later. Each static segment is marked as a specificity of "2," each 1727 | // dynamic segment is worth "1" specificity, and stars are worth "0" specificity. 1728 | var i, length = this._segments.length, specificity; 1729 | if (length == 0) { 1730 | // a single slash (or "empty segment" is as specific as a static segment 1731 | specificity += '2'; 1732 | } 1733 | else { 1734 | specificity = ''; 1735 | for (i = 0; i < length; i++) { 1736 | specificity += this._segments[i].specificity; 1737 | } 1738 | } 1739 | return specificity; 1740 | }; 1741 | ParamRoutePath.prototype._calculateHash = function () { 1742 | // this function is used to determine whether a route config path like `/foo/:id` collides with 1743 | // `/foo/:name` 1744 | var i, length = this._segments.length; 1745 | var hashParts = []; 1746 | for (i = 0; i < length; i++) { 1747 | hashParts.push(this._segments[i].hash); 1748 | } 1749 | return hashParts.join('/'); 1750 | }; 1751 | ParamRoutePath.prototype._assertValidPath = function (path) { 1752 | if (StringWrapper.contains(path, '#')) { 1753 | throw new BaseException("Path \"" + path + "\" should not include \"#\". Use \"HashLocationStrategy\" instead."); 1754 | } 1755 | var illegalCharacter = RegExpWrapper.firstMatch(ParamRoutePath.RESERVED_CHARS, path); 1756 | if (isPresent(illegalCharacter)) { 1757 | throw new BaseException("Path \"" + path + "\" contains \"" + illegalCharacter[0] + "\" which is not allowed in a route config."); 1758 | } 1759 | }; 1760 | ParamRoutePath.RESERVED_CHARS = RegExpWrapper.create('//|\\(|\\)|;|\\?|='); 1761 | return ParamRoutePath; 1762 | })(); 1763 | exports.ParamRoutePath = ParamRoutePath; 1764 | var REGEXP_PERCENT = /%/g; 1765 | var REGEXP_SLASH = /\//g; 1766 | var REGEXP_OPEN_PARENT = /\(/g; 1767 | var REGEXP_CLOSE_PARENT = /\)/g; 1768 | var REGEXP_SEMICOLON = /;/g; 1769 | function encodeDynamicSegment(value) { 1770 | if (isBlank(value)) { 1771 | return null; 1772 | } 1773 | value = StringWrapper.replaceAll(value, REGEXP_PERCENT, '%25'); 1774 | value = StringWrapper.replaceAll(value, REGEXP_SLASH, '%2F'); 1775 | value = StringWrapper.replaceAll(value, REGEXP_OPEN_PARENT, '%28'); 1776 | value = StringWrapper.replaceAll(value, REGEXP_CLOSE_PARENT, '%29'); 1777 | value = StringWrapper.replaceAll(value, REGEXP_SEMICOLON, '%3B'); 1778 | return value; 1779 | } 1780 | var REGEXP_ENC_SEMICOLON = /%3B/ig; 1781 | var REGEXP_ENC_CLOSE_PARENT = /%29/ig; 1782 | var REGEXP_ENC_OPEN_PARENT = /%28/ig; 1783 | var REGEXP_ENC_SLASH = /%2F/ig; 1784 | var REGEXP_ENC_PERCENT = /%25/ig; 1785 | function decodeDynamicSegment(value) { 1786 | if (isBlank(value)) { 1787 | return null; 1788 | } 1789 | value = StringWrapper.replaceAll(value, REGEXP_ENC_SEMICOLON, ';'); 1790 | value = StringWrapper.replaceAll(value, REGEXP_ENC_CLOSE_PARENT, ')'); 1791 | value = StringWrapper.replaceAll(value, REGEXP_ENC_OPEN_PARENT, '('); 1792 | value = StringWrapper.replaceAll(value, REGEXP_ENC_SLASH, '/'); 1793 | value = StringWrapper.replaceAll(value, REGEXP_ENC_PERCENT, '%'); 1794 | return value; 1795 | } 1796 | var route_path_1 = routerRequire('./route_path'); 1797 | var RegexRoutePath = (function () { 1798 | function RegexRoutePath(_reString, _serializer) { 1799 | this._reString = _reString; 1800 | this._serializer = _serializer; 1801 | this.terminal = true; 1802 | this.specificity = '2'; 1803 | this.hash = this._reString; 1804 | this._regex = RegExpWrapper.create(this._reString); 1805 | } 1806 | RegexRoutePath.prototype.matchUrl = function (url) { 1807 | var urlPath = url.toString(); 1808 | var params = {}; 1809 | var matcher = RegExpWrapper.matcher(this._regex, urlPath); 1810 | var match = RegExpMatcherWrapper.next(matcher); 1811 | if (isBlank(match)) { 1812 | return null; 1813 | } 1814 | for (var i = 0; i < match.length; i += 1) { 1815 | params[i.toString()] = match[i]; 1816 | } 1817 | return new route_path_1.MatchedUrl(urlPath, [], params, [], null); 1818 | }; 1819 | RegexRoutePath.prototype.generateUrl = function (params) { return this._serializer(params); }; 1820 | RegexRoutePath.prototype.toString = function () { return this._reString; }; 1821 | return RegexRoutePath; 1822 | })(); 1823 | exports.RegexRoutePath = RegexRoutePath; 1824 | var __extends = (this && this.__extends) || function (d, b) { 1825 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 1826 | function __() { this.constructor = d; } 1827 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 1828 | }; 1829 | /** 1830 | * `RouteParams` is an immutable map of parameters for the given route 1831 | * based on the url matcher and optional parameters for that route. 1832 | * 1833 | * You can inject `RouteParams` into the constructor of a component to use it. 1834 | * 1835 | * ### Example 1836 | * 1837 | * ``` 1838 | * import {Component} from 'angular2/core'; 1839 | * import {bootstrap} from 'angular2/platform/browser'; 1840 | * import {Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig, RouteParams} from 1841 | * 'angular2/router'; 1842 | * 1843 | * @Component({directives: [ROUTER_DIRECTIVES]}) 1844 | * @RouteConfig([ 1845 | * {path: '/user/:id', component: UserCmp, name: 'UserCmp'}, 1846 | * ]) 1847 | * class AppCmp {} 1848 | * 1849 | * @Component({ template: 'user: {{id}}' }) 1850 | * class UserCmp { 1851 | * id: string; 1852 | * constructor(params: RouteParams) { 1853 | * this.id = params.get('id'); 1854 | * } 1855 | * } 1856 | * 1857 | * bootstrap(AppCmp, ROUTER_PROVIDERS); 1858 | * ``` 1859 | */ 1860 | var RouteParams = (function () { 1861 | function RouteParams(params) { 1862 | this.params = params; 1863 | } 1864 | RouteParams.prototype.get = function (param) { return normalizeBlank(StringMapWrapper.get(this.params, param)); }; 1865 | return RouteParams; 1866 | })(); 1867 | exports.RouteParams = RouteParams; 1868 | /** 1869 | * `RouteData` is an immutable map of additional data you can configure in your {@link Route}. 1870 | * 1871 | * You can inject `RouteData` into the constructor of a component to use it. 1872 | * 1873 | * ### Example 1874 | * 1875 | * ``` 1876 | * import {Component} from 'angular2/core'; 1877 | * import {bootstrap} from 'angular2/platform/browser'; 1878 | * import {Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig, RouteData} from 1879 | * 'angular2/router'; 1880 | * 1881 | * @Component({directives: [ROUTER_DIRECTIVES]}) 1882 | * @RouteConfig([ 1883 | * {path: '/user/:id', component: UserCmp, name: 'UserCmp', data: {isAdmin: true}}, 1884 | * ]) 1885 | * class AppCmp {} 1886 | * 1887 | * @Component({ 1888 | * ..., 1889 | * template: 'user: {{isAdmin}}' 1890 | * }) 1891 | * class UserCmp { 1892 | * string: isAdmin; 1893 | * constructor(data: RouteData) { 1894 | * this.isAdmin = data.get('isAdmin'); 1895 | * } 1896 | * } 1897 | * 1898 | * bootstrap(AppCmp, ROUTER_PROVIDERS); 1899 | * ``` 1900 | */ 1901 | var RouteData = (function () { 1902 | function RouteData(data) { 1903 | if (data === void 0) { data = CONST_EXPR({}); } 1904 | this.data = data; 1905 | } 1906 | RouteData.prototype.get = function (key) { return normalizeBlank(StringMapWrapper.get(this.data, key)); }; 1907 | return RouteData; 1908 | })(); 1909 | exports.RouteData = RouteData; 1910 | exports.BLANK_ROUTE_DATA = new RouteData(); 1911 | /** 1912 | * `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed 1913 | * to transition each component in the app to a given route, including all auxiliary routes. 1914 | * 1915 | * `Instruction`s can be created using {@link Router#generate}, and can be used to 1916 | * perform route changes with {@link Router#navigateByInstruction}. 1917 | * 1918 | * ### Example 1919 | * 1920 | * ``` 1921 | * import {Component} from 'angular2/core'; 1922 | * import {bootstrap} from 'angular2/platform/browser'; 1923 | * import {Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from 'angular2/router'; 1924 | * 1925 | * @Component({directives: [ROUTER_DIRECTIVES]}) 1926 | * @RouteConfig([ 1927 | * {...}, 1928 | * ]) 1929 | * class AppCmp { 1930 | * constructor(router: Router) { 1931 | * var instruction = router.generate(['/MyRoute']); 1932 | * router.navigateByInstruction(instruction); 1933 | * } 1934 | * } 1935 | * 1936 | * bootstrap(AppCmp, ROUTER_PROVIDERS); 1937 | * ``` 1938 | */ 1939 | var Instruction = (function () { 1940 | function Instruction(component, child, auxInstruction) { 1941 | this.component = component; 1942 | this.child = child; 1943 | this.auxInstruction = auxInstruction; 1944 | } 1945 | Object.defineProperty(Instruction.prototype, "urlPath", { 1946 | get: function () { return isPresent(this.component) ? this.component.urlPath : ''; }, 1947 | enumerable: true, 1948 | configurable: true 1949 | }); 1950 | Object.defineProperty(Instruction.prototype, "urlParams", { 1951 | get: function () { return isPresent(this.component) ? this.component.urlParams : []; }, 1952 | enumerable: true, 1953 | configurable: true 1954 | }); 1955 | Object.defineProperty(Instruction.prototype, "specificity", { 1956 | get: function () { 1957 | var total = ''; 1958 | if (isPresent(this.component)) { 1959 | total += this.component.specificity; 1960 | } 1961 | if (isPresent(this.child)) { 1962 | total += this.child.specificity; 1963 | } 1964 | return total; 1965 | }, 1966 | enumerable: true, 1967 | configurable: true 1968 | }); 1969 | /** 1970 | * converts the instruction into a URL string 1971 | */ 1972 | Instruction.prototype.toRootUrl = function () { return this.toUrlPath() + this.toUrlQuery(); }; 1973 | /** @internal */ 1974 | Instruction.prototype._toNonRootUrl = function () { 1975 | return this._stringifyPathMatrixAuxPrefixed() + 1976 | (isPresent(this.child) ? this.child._toNonRootUrl() : ''); 1977 | }; 1978 | Instruction.prototype.toUrlQuery = function () { return this.urlParams.length > 0 ? ('?' + this.urlParams.join('&')) : ''; }; 1979 | /** 1980 | * Returns a new instruction that shares the state of the existing instruction, but with 1981 | * the given child {@link Instruction} replacing the existing child. 1982 | */ 1983 | Instruction.prototype.replaceChild = function (child) { 1984 | return new ResolvedInstruction(this.component, child, this.auxInstruction); 1985 | }; 1986 | /** 1987 | * If the final URL for the instruction is `` 1988 | */ 1989 | Instruction.prototype.toUrlPath = function () { 1990 | return this.urlPath + this._stringifyAux() + 1991 | (isPresent(this.child) ? this.child._toNonRootUrl() : ''); 1992 | }; 1993 | // default instructions override these 1994 | Instruction.prototype.toLinkUrl = function () { 1995 | return this.urlPath + this._stringifyAux() + 1996 | (isPresent(this.child) ? this.child._toLinkUrl() : '') + this.toUrlQuery(); 1997 | }; 1998 | // this is the non-root version (called recursively) 1999 | /** @internal */ 2000 | Instruction.prototype._toLinkUrl = function () { 2001 | return this._stringifyPathMatrixAuxPrefixed() + 2002 | (isPresent(this.child) ? this.child._toLinkUrl() : ''); 2003 | }; 2004 | /** @internal */ 2005 | Instruction.prototype._stringifyPathMatrixAuxPrefixed = function () { 2006 | var primary = this._stringifyPathMatrixAux(); 2007 | if (primary.length > 0) { 2008 | primary = '/' + primary; 2009 | } 2010 | return primary; 2011 | }; 2012 | /** @internal */ 2013 | Instruction.prototype._stringifyMatrixParams = function () { 2014 | return this.urlParams.length > 0 ? (';' + this.urlParams.join(';')) : ''; 2015 | }; 2016 | /** @internal */ 2017 | Instruction.prototype._stringifyPathMatrixAux = function () { 2018 | if (isBlank(this.component)) { 2019 | return ''; 2020 | } 2021 | return this.urlPath + this._stringifyMatrixParams() + this._stringifyAux(); 2022 | }; 2023 | /** @internal */ 2024 | Instruction.prototype._stringifyAux = function () { 2025 | var routes = []; 2026 | StringMapWrapper.forEach(this.auxInstruction, function (auxInstruction, _) { 2027 | routes.push(auxInstruction._stringifyPathMatrixAux()); 2028 | }); 2029 | if (routes.length > 0) { 2030 | return '(' + routes.join('//') + ')'; 2031 | } 2032 | return ''; 2033 | }; 2034 | return Instruction; 2035 | })(); 2036 | exports.Instruction = Instruction; 2037 | /** 2038 | * a resolved instruction has an outlet instruction for itself, but maybe not for... 2039 | */ 2040 | var ResolvedInstruction = (function (_super) { 2041 | __extends(ResolvedInstruction, _super); 2042 | function ResolvedInstruction(component, child, auxInstruction) { 2043 | _super.call(this, component, child, auxInstruction); 2044 | } 2045 | ResolvedInstruction.prototype.resolveComponent = function () { 2046 | return PromiseWrapper.resolve(this.component); 2047 | }; 2048 | return ResolvedInstruction; 2049 | })(Instruction); 2050 | exports.ResolvedInstruction = ResolvedInstruction; 2051 | /** 2052 | * Represents a resolved default route 2053 | */ 2054 | var DefaultInstruction = (function (_super) { 2055 | __extends(DefaultInstruction, _super); 2056 | function DefaultInstruction(component, child) { 2057 | _super.call(this, component, child, {}); 2058 | } 2059 | DefaultInstruction.prototype.toLinkUrl = function () { return ''; }; 2060 | /** @internal */ 2061 | DefaultInstruction.prototype._toLinkUrl = function () { return ''; }; 2062 | return DefaultInstruction; 2063 | })(ResolvedInstruction); 2064 | exports.DefaultInstruction = DefaultInstruction; 2065 | /** 2066 | * Represents a component that may need to do some redirection or lazy loading at a later time. 2067 | */ 2068 | var UnresolvedInstruction = (function (_super) { 2069 | __extends(UnresolvedInstruction, _super); 2070 | function UnresolvedInstruction(_resolver, _urlPath, _urlParams) { 2071 | if (_urlPath === void 0) { _urlPath = ''; } 2072 | if (_urlParams === void 0) { _urlParams = CONST_EXPR([]); } 2073 | _super.call(this, null, null, {}); 2074 | this._resolver = _resolver; 2075 | this._urlPath = _urlPath; 2076 | this._urlParams = _urlParams; 2077 | } 2078 | Object.defineProperty(UnresolvedInstruction.prototype, "urlPath", { 2079 | get: function () { 2080 | if (isPresent(this.component)) { 2081 | return this.component.urlPath; 2082 | } 2083 | if (isPresent(this._urlPath)) { 2084 | return this._urlPath; 2085 | } 2086 | return ''; 2087 | }, 2088 | enumerable: true, 2089 | configurable: true 2090 | }); 2091 | Object.defineProperty(UnresolvedInstruction.prototype, "urlParams", { 2092 | get: function () { 2093 | if (isPresent(this.component)) { 2094 | return this.component.urlParams; 2095 | } 2096 | if (isPresent(this._urlParams)) { 2097 | return this._urlParams; 2098 | } 2099 | return []; 2100 | }, 2101 | enumerable: true, 2102 | configurable: true 2103 | }); 2104 | UnresolvedInstruction.prototype.resolveComponent = function () { 2105 | var _this = this; 2106 | if (isPresent(this.component)) { 2107 | return PromiseWrapper.resolve(this.component); 2108 | } 2109 | return this._resolver().then(function (instruction) { 2110 | _this.child = isPresent(instruction) ? instruction.child : null; 2111 | return _this.component = isPresent(instruction) ? instruction.component : null; 2112 | }); 2113 | }; 2114 | return UnresolvedInstruction; 2115 | })(Instruction); 2116 | exports.UnresolvedInstruction = UnresolvedInstruction; 2117 | var RedirectInstruction = (function (_super) { 2118 | __extends(RedirectInstruction, _super); 2119 | function RedirectInstruction(component, child, auxInstruction, _specificity) { 2120 | _super.call(this, component, child, auxInstruction); 2121 | this._specificity = _specificity; 2122 | } 2123 | Object.defineProperty(RedirectInstruction.prototype, "specificity", { 2124 | get: function () { return this._specificity; }, 2125 | enumerable: true, 2126 | configurable: true 2127 | }); 2128 | return RedirectInstruction; 2129 | })(ResolvedInstruction); 2130 | exports.RedirectInstruction = RedirectInstruction; 2131 | /** 2132 | * A `ComponentInstruction` represents the route state for a single component. 2133 | * 2134 | * `ComponentInstructions` is a public API. Instances of `ComponentInstruction` are passed 2135 | * to route lifecycle hooks, like {@link CanActivate}. 2136 | * 2137 | * `ComponentInstruction`s are [hash consed](https://en.wikipedia.org/wiki/Hash_consing). You should 2138 | * never construct one yourself with "new." Instead, rely on {@link Router/RouteRecognizer} to 2139 | * construct `ComponentInstruction`s. 2140 | * 2141 | * You should not modify this object. It should be treated as immutable. 2142 | */ 2143 | var ComponentInstruction = (function () { 2144 | /** 2145 | * @internal 2146 | */ 2147 | function ComponentInstruction(urlPath, urlParams, data, componentType, terminal, specificity, params, routeName) { 2148 | if (params === void 0) { params = null; } 2149 | this.urlPath = urlPath; 2150 | this.urlParams = urlParams; 2151 | this.componentType = componentType; 2152 | this.terminal = terminal; 2153 | this.specificity = specificity; 2154 | this.params = params; 2155 | this.routeName = routeName; 2156 | this.reuse = false; 2157 | this.routeData = isPresent(data) ? data : exports.BLANK_ROUTE_DATA; 2158 | } 2159 | return ComponentInstruction; 2160 | })(); 2161 | exports.ComponentInstruction = ComponentInstruction; 2162 | var core_1 = routerRequire('angular2/core'); 2163 | var route_config_impl_1 = routerRequire('./route_config/route_config_impl'); 2164 | var rules_1 = routerRequire('./rules/rules'); 2165 | var rule_set_1 = routerRequire('./rules/rule_set'); 2166 | var instruction_1 = routerRequire('./instruction'); 2167 | var route_config_normalizer_1 = routerRequire('./route_config/route_config_normalizer'); 2168 | var url_parser_1 = routerRequire('./url_parser'); 2169 | var _resolveToNull = PromiseWrapper.resolve(null); 2170 | // A LinkItemArray is an array, which describes a set of routes 2171 | // The items in the array are found in groups: 2172 | // - the first item is the name of the route 2173 | // - the next items are: 2174 | // - an object containing parameters 2175 | // - or an array describing an aux route 2176 | // export type LinkRouteItem = string | Object; 2177 | // export type LinkItem = LinkRouteItem | Array; 2178 | // export type LinkItemArray = Array; 2179 | /** 2180 | * Token used to bind the component with the top-level {@link RouteConfig}s for the 2181 | * application. 2182 | * 2183 | * ### Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm)) 2184 | * 2185 | * ``` 2186 | * import {Component} from 'angular2/core'; 2187 | * import { 2188 | * ROUTER_DIRECTIVES, 2189 | * ROUTER_PROVIDERS, 2190 | * RouteConfig 2191 | * } from 'angular2/router'; 2192 | * 2193 | * @Component({directives: [ROUTER_DIRECTIVES]}) 2194 | * @RouteConfig([ 2195 | * {...}, 2196 | * ]) 2197 | * class AppCmp { 2198 | * // ... 2199 | * } 2200 | * 2201 | * bootstrap(AppCmp, [ROUTER_PROVIDERS]); 2202 | * ``` 2203 | */ 2204 | exports.ROUTER_PRIMARY_COMPONENT = CONST_EXPR(new core_1.OpaqueToken('RouterPrimaryComponent')); 2205 | /** 2206 | * The RouteRegistry holds route configurations for each component in an Angular app. 2207 | * It is responsible for creating Instructions from URLs, and generating URLs based on route and 2208 | * parameters. 2209 | */ 2210 | var RouteRegistry = (function () { 2211 | function RouteRegistry(_rootComponent) { 2212 | this._rootComponent = _rootComponent; 2213 | this._rules = new Map(); 2214 | } 2215 | /** 2216 | * Given a component and a configuration object, add the route to this registry 2217 | */ 2218 | RouteRegistry.prototype.config = function (parentComponent, config) { 2219 | config = route_config_normalizer_1.normalizeRouteConfig(config, this); 2220 | // this is here because Dart type guard reasons 2221 | if (config instanceof route_config_impl_1.Route) { 2222 | route_config_normalizer_1.assertComponentExists(config.component, config.path); 2223 | } 2224 | else if (config instanceof route_config_impl_1.AuxRoute) { 2225 | route_config_normalizer_1.assertComponentExists(config.component, config.path); 2226 | } 2227 | var rules = this._rules.get(parentComponent); 2228 | if (isBlank(rules)) { 2229 | rules = new rule_set_1.RuleSet(); 2230 | this._rules.set(parentComponent, rules); 2231 | } 2232 | var terminal = rules.config(config); 2233 | if (config instanceof route_config_impl_1.Route) { 2234 | if (terminal) { 2235 | assertTerminalComponent(config.component, config.path); 2236 | } 2237 | else { 2238 | this.configFromComponent(config.component); 2239 | } 2240 | } 2241 | }; 2242 | /** 2243 | * Reads the annotations of a component and configures the registry based on them 2244 | */ 2245 | RouteRegistry.prototype.configFromComponent = function (component) { 2246 | var _this = this; 2247 | if (!isType(component)) { 2248 | return; 2249 | } 2250 | // Don't read the annotations from a type more than once – 2251 | // this prevents an infinite loop if a component routes recursively. 2252 | if (this._rules.has(component)) { 2253 | return; 2254 | } 2255 | var annotations = reflector.annotations(component); 2256 | if (isPresent(annotations)) { 2257 | for (var i = 0; i < annotations.length; i++) { 2258 | var annotation = annotations[i]; 2259 | if (annotation instanceof route_config_impl_1.RouteConfig) { 2260 | var routeCfgs = annotation.configs; 2261 | routeCfgs.forEach(function (config) { return _this.config(component, config); }); 2262 | } 2263 | } 2264 | } 2265 | }; 2266 | /** 2267 | * Given a URL and a parent component, return the most specific instruction for navigating 2268 | * the application into the state specified by the url 2269 | */ 2270 | RouteRegistry.prototype.recognize = function (url, ancestorInstructions) { 2271 | var parsedUrl = url_parser_1.parser.parse(url); 2272 | return this._recognize(parsedUrl, []); 2273 | }; 2274 | /** 2275 | * Recognizes all parent-child routes, but creates unresolved auxiliary routes 2276 | */ 2277 | RouteRegistry.prototype._recognize = function (parsedUrl, ancestorInstructions, _aux) { 2278 | var _this = this; 2279 | if (_aux === void 0) { _aux = false; } 2280 | var parentInstruction = ListWrapper.last(ancestorInstructions); 2281 | var parentComponent = isPresent(parentInstruction) ? parentInstruction.component.componentType : 2282 | this._rootComponent; 2283 | var rules = this._rules.get(parentComponent); 2284 | if (isBlank(rules)) { 2285 | return _resolveToNull; 2286 | } 2287 | // Matches some beginning part of the given URL 2288 | var possibleMatches = _aux ? rules.recognizeAuxiliary(parsedUrl) : rules.recognize(parsedUrl); 2289 | var matchPromises = possibleMatches.map(function (candidate) { return candidate.then(function (candidate) { 2290 | if (candidate instanceof rules_1.PathMatch) { 2291 | var auxParentInstructions = ancestorInstructions.length > 0 ? [ListWrapper.last(ancestorInstructions)] : []; 2292 | var auxInstructions = _this._auxRoutesToUnresolved(candidate.remainingAux, auxParentInstructions); 2293 | var instruction = new instruction_1.ResolvedInstruction(candidate.instruction, null, auxInstructions); 2294 | if (isBlank(candidate.instruction) || candidate.instruction.terminal) { 2295 | return instruction; 2296 | } 2297 | var newAncestorInstructions = ancestorInstructions.concat([instruction]); 2298 | return _this._recognize(candidate.remaining, newAncestorInstructions) 2299 | .then(function (childInstruction) { 2300 | if (isBlank(childInstruction)) { 2301 | return null; 2302 | } 2303 | // redirect instructions are already absolute 2304 | if (childInstruction instanceof instruction_1.RedirectInstruction) { 2305 | return childInstruction; 2306 | } 2307 | instruction.child = childInstruction; 2308 | return instruction; 2309 | }); 2310 | } 2311 | if (candidate instanceof rules_1.RedirectMatch) { 2312 | var instruction = _this.generate(candidate.redirectTo, ancestorInstructions.concat([null])); 2313 | return new instruction_1.RedirectInstruction(instruction.component, instruction.child, instruction.auxInstruction, candidate.specificity); 2314 | } 2315 | }); }); 2316 | if ((isBlank(parsedUrl) || parsedUrl.path == '') && possibleMatches.length == 0) { 2317 | return PromiseWrapper.resolve(this.generateDefault(parentComponent)); 2318 | } 2319 | return PromiseWrapper.all(matchPromises).then(mostSpecific); 2320 | }; 2321 | RouteRegistry.prototype._auxRoutesToUnresolved = function (auxRoutes, parentInstructions) { 2322 | var _this = this; 2323 | var unresolvedAuxInstructions = {}; 2324 | auxRoutes.forEach(function (auxUrl) { 2325 | unresolvedAuxInstructions[auxUrl.path] = new instruction_1.UnresolvedInstruction(function () { return _this._recognize(auxUrl, parentInstructions, true); }); 2326 | }); 2327 | return unresolvedAuxInstructions; 2328 | }; 2329 | /** 2330 | * Given a normalized list with component names and params like: `['user', {id: 3 }]` 2331 | * generates a url with a leading slash relative to the provided `parentComponent`. 2332 | * 2333 | * If the optional param `_aux` is `true`, then we generate starting at an auxiliary 2334 | * route boundary. 2335 | */ 2336 | RouteRegistry.prototype.generate = function (linkParams, ancestorInstructions, _aux) { 2337 | if (_aux === void 0) { _aux = false; } 2338 | var params = splitAndFlattenLinkParams(linkParams); 2339 | var prevInstruction; 2340 | // The first segment should be either '.' (generate from parent) or '' (generate from root). 2341 | // When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''. 2342 | if (ListWrapper.first(params) == '') { 2343 | params.shift(); 2344 | prevInstruction = ListWrapper.first(ancestorInstructions); 2345 | ancestorInstructions = []; 2346 | } 2347 | else { 2348 | prevInstruction = ancestorInstructions.length > 0 ? ancestorInstructions.pop() : null; 2349 | if (ListWrapper.first(params) == '.') { 2350 | params.shift(); 2351 | } 2352 | else if (ListWrapper.first(params) == '..') { 2353 | while (ListWrapper.first(params) == '..') { 2354 | if (ancestorInstructions.length <= 0) { 2355 | throw new BaseException("Link \"" + ListWrapper.toJSON(linkParams) + "\" has too many \"../\" segments."); 2356 | } 2357 | prevInstruction = ancestorInstructions.pop(); 2358 | params = ListWrapper.slice(params, 1); 2359 | } 2360 | } 2361 | else { 2362 | // we must only peak at the link param, and not consume it 2363 | var routeName = ListWrapper.first(params); 2364 | var parentComponentType = this._rootComponent; 2365 | var grandparentComponentType = null; 2366 | if (ancestorInstructions.length > 1) { 2367 | var parentComponentInstruction = ancestorInstructions[ancestorInstructions.length - 1]; 2368 | var grandComponentInstruction = ancestorInstructions[ancestorInstructions.length - 2]; 2369 | parentComponentType = parentComponentInstruction.component.componentType; 2370 | grandparentComponentType = grandComponentInstruction.component.componentType; 2371 | } 2372 | else if (ancestorInstructions.length == 1) { 2373 | parentComponentType = ancestorInstructions[0].component.componentType; 2374 | grandparentComponentType = this._rootComponent; 2375 | } 2376 | // For a link with no leading `./`, `/`, or `../`, we look for a sibling and child. 2377 | // If both exist, we throw. Otherwise, we prefer whichever exists. 2378 | var childRouteExists = this.hasRoute(routeName, parentComponentType); 2379 | var parentRouteExists = isPresent(grandparentComponentType) && 2380 | this.hasRoute(routeName, grandparentComponentType); 2381 | if (parentRouteExists && childRouteExists) { 2382 | var msg = "Link \"" + ListWrapper.toJSON(linkParams) + "\" is ambiguous, use \"./\" or \"../\" to disambiguate."; 2383 | throw new BaseException(msg); 2384 | } 2385 | if (parentRouteExists) { 2386 | prevInstruction = ancestorInstructions.pop(); 2387 | } 2388 | } 2389 | } 2390 | if (params[params.length - 1] == '') { 2391 | params.pop(); 2392 | } 2393 | if (params.length > 0 && params[0] == '') { 2394 | params.shift(); 2395 | } 2396 | if (params.length < 1) { 2397 | var msg = "Link \"" + ListWrapper.toJSON(linkParams) + "\" must include a route name."; 2398 | throw new BaseException(msg); 2399 | } 2400 | var generatedInstruction = this._generate(params, ancestorInstructions, prevInstruction, _aux, linkParams); 2401 | // we don't clone the first (root) element 2402 | for (var i = ancestorInstructions.length - 1; i >= 0; i--) { 2403 | var ancestorInstruction = ancestorInstructions[i]; 2404 | if (isBlank(ancestorInstruction)) { 2405 | break; 2406 | } 2407 | generatedInstruction = ancestorInstruction.replaceChild(generatedInstruction); 2408 | } 2409 | return generatedInstruction; 2410 | }; 2411 | /* 2412 | * Internal helper that does not make any assertions about the beginning of the link DSL. 2413 | * `ancestorInstructions` are parents that will be cloned. 2414 | * `prevInstruction` is the existing instruction that would be replaced, but which might have 2415 | * aux routes that need to be cloned. 2416 | */ 2417 | RouteRegistry.prototype._generate = function (linkParams, ancestorInstructions, prevInstruction, _aux, _originalLink) { 2418 | var _this = this; 2419 | if (_aux === void 0) { _aux = false; } 2420 | var parentComponentType = this._rootComponent; 2421 | var componentInstruction = null; 2422 | var auxInstructions = {}; 2423 | var parentInstruction = ListWrapper.last(ancestorInstructions); 2424 | if (isPresent(parentInstruction) && isPresent(parentInstruction.component)) { 2425 | parentComponentType = parentInstruction.component.componentType; 2426 | } 2427 | if (linkParams.length == 0) { 2428 | var defaultInstruction = this.generateDefault(parentComponentType); 2429 | if (isBlank(defaultInstruction)) { 2430 | throw new BaseException("Link \"" + ListWrapper.toJSON(_originalLink) + "\" does not resolve to a terminal instruction."); 2431 | } 2432 | return defaultInstruction; 2433 | } 2434 | // for non-aux routes, we want to reuse the predecessor's existing primary and aux routes 2435 | // and only override routes for which the given link DSL provides 2436 | if (isPresent(prevInstruction) && !_aux) { 2437 | auxInstructions = StringMapWrapper.merge(prevInstruction.auxInstruction, auxInstructions); 2438 | componentInstruction = prevInstruction.component; 2439 | } 2440 | var rules = this._rules.get(parentComponentType); 2441 | if (isBlank(rules)) { 2442 | throw new BaseException("Component \"" + getTypeNameForDebugging(parentComponentType) + "\" has no route config."); 2443 | } 2444 | var linkParamIndex = 0; 2445 | var routeParams = {}; 2446 | // first, recognize the primary route if one is provided 2447 | if (linkParamIndex < linkParams.length && isString(linkParams[linkParamIndex])) { 2448 | var routeName = linkParams[linkParamIndex]; 2449 | if (routeName == '' || routeName == '.' || routeName == '..') { 2450 | throw new BaseException("\"" + routeName + "/\" is only allowed at the beginning of a link DSL."); 2451 | } 2452 | linkParamIndex += 1; 2453 | if (linkParamIndex < linkParams.length) { 2454 | var linkParam = linkParams[linkParamIndex]; 2455 | if (isStringMap(linkParam) && !isArray(linkParam)) { 2456 | routeParams = linkParam; 2457 | linkParamIndex += 1; 2458 | } 2459 | } 2460 | var routeRecognizer = (_aux ? rules.auxRulesByName : rules.rulesByName).get(routeName); 2461 | if (isBlank(routeRecognizer)) { 2462 | throw new BaseException("Component \"" + getTypeNameForDebugging(parentComponentType) + "\" has no route named \"" + routeName + "\"."); 2463 | } 2464 | // Create an "unresolved instruction" for async routes 2465 | // we'll figure out the rest of the route when we resolve the instruction and 2466 | // perform a navigation 2467 | if (isBlank(routeRecognizer.handler.componentType)) { 2468 | var generatedUrl = routeRecognizer.generateComponentPathValues(routeParams); 2469 | return new instruction_1.UnresolvedInstruction(function () { 2470 | return routeRecognizer.handler.resolveComponentType().then(function (_) { 2471 | return _this._generate(linkParams, ancestorInstructions, prevInstruction, _aux, _originalLink); 2472 | }); 2473 | }, generatedUrl.urlPath, url_parser_1.convertUrlParamsToArray(generatedUrl.urlParams)); 2474 | } 2475 | componentInstruction = _aux ? rules.generateAuxiliary(routeName, routeParams) : 2476 | rules.generate(routeName, routeParams); 2477 | } 2478 | // Next, recognize auxiliary instructions. 2479 | // If we have an ancestor instruction, we preserve whatever aux routes are active from it. 2480 | while (linkParamIndex < linkParams.length && isArray(linkParams[linkParamIndex])) { 2481 | var auxParentInstruction = [parentInstruction]; 2482 | var auxInstruction = this._generate(linkParams[linkParamIndex], auxParentInstruction, null, true, _originalLink); 2483 | // TODO: this will not work for aux routes with parameters or multiple segments 2484 | auxInstructions[auxInstruction.component.urlPath] = auxInstruction; 2485 | linkParamIndex += 1; 2486 | } 2487 | var instruction = new instruction_1.ResolvedInstruction(componentInstruction, null, auxInstructions); 2488 | // If the component is sync, we can generate resolved child route instructions 2489 | // If not, we'll resolve the instructions at navigation time 2490 | if (isPresent(componentInstruction) && isPresent(componentInstruction.componentType)) { 2491 | var childInstruction = null; 2492 | if (componentInstruction.terminal) { 2493 | if (linkParamIndex >= linkParams.length) { 2494 | } 2495 | } 2496 | else { 2497 | var childAncestorComponents = ancestorInstructions.concat([instruction]); 2498 | var remainingLinkParams = linkParams.slice(linkParamIndex); 2499 | childInstruction = this._generate(remainingLinkParams, childAncestorComponents, null, false, _originalLink); 2500 | } 2501 | instruction.child = childInstruction; 2502 | } 2503 | return instruction; 2504 | }; 2505 | RouteRegistry.prototype.hasRoute = function (name, parentComponent) { 2506 | var rules = this._rules.get(parentComponent); 2507 | if (isBlank(rules)) { 2508 | return false; 2509 | } 2510 | return rules.hasRoute(name); 2511 | }; 2512 | RouteRegistry.prototype.generateDefault = function (componentCursor) { 2513 | var _this = this; 2514 | if (isBlank(componentCursor)) { 2515 | return null; 2516 | } 2517 | var rules = this._rules.get(componentCursor); 2518 | if (isBlank(rules) || isBlank(rules.defaultRule)) { 2519 | return null; 2520 | } 2521 | var defaultChild = null; 2522 | if (isPresent(rules.defaultRule.handler.componentType)) { 2523 | var componentInstruction = rules.defaultRule.generate({}); 2524 | if (!rules.defaultRule.terminal) { 2525 | defaultChild = this.generateDefault(rules.defaultRule.handler.componentType); 2526 | } 2527 | return new instruction_1.DefaultInstruction(componentInstruction, defaultChild); 2528 | } 2529 | return new instruction_1.UnresolvedInstruction(function () { 2530 | return rules.defaultRule.handler.resolveComponentType().then(function (_) { return _this.generateDefault(componentCursor); }); 2531 | }); 2532 | }; 2533 | return RouteRegistry; 2534 | })(); 2535 | exports.RouteRegistry = RouteRegistry; 2536 | /* 2537 | * Given: ['/a/b', {c: 2}] 2538 | * Returns: ['', 'a', 'b', {c: 2}] 2539 | */ 2540 | function splitAndFlattenLinkParams(linkParams) { 2541 | var accumulation = []; 2542 | linkParams.forEach(function (item) { 2543 | if (isString(item)) { 2544 | var strItem = item; 2545 | accumulation = accumulation.concat(strItem.split('/')); 2546 | } 2547 | else { 2548 | accumulation.push(item); 2549 | } 2550 | }); 2551 | return accumulation; 2552 | } 2553 | /* 2554 | * Given a list of instructions, returns the most specific instruction 2555 | */ 2556 | function mostSpecific(instructions) { 2557 | instructions = instructions.filter(function (instruction) { return isPresent(instruction); }); 2558 | if (instructions.length == 0) { 2559 | return null; 2560 | } 2561 | if (instructions.length == 1) { 2562 | return instructions[0]; 2563 | } 2564 | var first = instructions[0]; 2565 | var rest = instructions.slice(1); 2566 | return rest.reduce(function (instruction, contender) { 2567 | if (compareSpecificityStrings(contender.specificity, instruction.specificity) == -1) { 2568 | return contender; 2569 | } 2570 | return instruction; 2571 | }, first); 2572 | } 2573 | /* 2574 | * Expects strings to be in the form of "[0-2]+" 2575 | * Returns -1 if string A should be sorted above string B, 1 if it should be sorted after, 2576 | * or 0 if they are the same. 2577 | */ 2578 | function compareSpecificityStrings(a, b) { 2579 | var l = Math.min(a.length, b.length); 2580 | for (var i = 0; i < l; i += 1) { 2581 | var ai = StringWrapper.charCodeAt(a, i); 2582 | var bi = StringWrapper.charCodeAt(b, i); 2583 | var difference = bi - ai; 2584 | if (difference != 0) { 2585 | return difference; 2586 | } 2587 | } 2588 | return a.length - b.length; 2589 | } 2590 | function assertTerminalComponent(component, path) { 2591 | if (!isType(component)) { 2592 | return; 2593 | } 2594 | var annotations = reflector.annotations(component); 2595 | if (isPresent(annotations)) { 2596 | for (var i = 0; i < annotations.length; i++) { 2597 | var annotation = annotations[i]; 2598 | if (annotation instanceof route_config_impl_1.RouteConfig) { 2599 | throw new BaseException("Child routes are not allowed for \"" + path + "\". Use \"...\" on the parent's route path."); 2600 | } 2601 | } 2602 | } 2603 | } 2604 | var __extends = (this && this.__extends) || function (d, b) { 2605 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 2606 | function __() { this.constructor = d; } 2607 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 2608 | }; 2609 | var route_lifecycle_reflector_1 = routerRequire('./lifecycle/route_lifecycle_reflector'); 2610 | var _resolveToTrue = PromiseWrapper.resolve(true); 2611 | var _resolveToFalse = PromiseWrapper.resolve(false); 2612 | /** 2613 | * The `Router` is responsible for mapping URLs to components. 2614 | * 2615 | * You can see the state of the router by inspecting the read-only field `router.navigating`. 2616 | * This may be useful for showing a spinner, for instance. 2617 | * 2618 | * ## Concepts 2619 | * 2620 | * Routers and component instances have a 1:1 correspondence. 2621 | * 2622 | * The router holds reference to a number of {@link RouterOutlet}. 2623 | * An outlet is a placeholder that the router dynamically fills in depending on the current URL. 2624 | * 2625 | * When the router navigates from a URL, it must first recognize it and serialize it into an 2626 | * `Instruction`. 2627 | * The router uses the `RouteRegistry` to get an `Instruction`. 2628 | */ 2629 | var Router = (function () { 2630 | function Router(registry, parent, hostComponent, root) { 2631 | this.registry = registry; 2632 | this.parent = parent; 2633 | this.hostComponent = hostComponent; 2634 | this.root = root; 2635 | this.navigating = false; 2636 | /** 2637 | * The current `Instruction` for the router 2638 | */ 2639 | this.currentInstruction = null; 2640 | this._currentNavigation = _resolveToTrue; 2641 | this._outlet = null; 2642 | this._auxRouters = new Map(); 2643 | this._subject = new EventEmitter(); 2644 | } 2645 | /** 2646 | * Constructs a child router. You probably don't need to use this unless you're writing a reusable 2647 | * component. 2648 | */ 2649 | Router.prototype.childRouter = function (hostComponent) { 2650 | return this._childRouter = new ChildRouter(this, hostComponent); 2651 | }; 2652 | /** 2653 | * Constructs a child router. You probably don't need to use this unless you're writing a reusable 2654 | * component. 2655 | */ 2656 | Router.prototype.auxRouter = function (hostComponent) { return new ChildRouter(this, hostComponent); }; 2657 | /** 2658 | * Register an outlet to be notified of primary route changes. 2659 | * 2660 | * You probably don't need to use this unless you're writing a reusable component. 2661 | */ 2662 | Router.prototype.registerPrimaryOutlet = function (outlet) { 2663 | if (isPresent(outlet.name)) { 2664 | throw new BaseException("registerPrimaryOutlet expects to be called with an unnamed outlet."); 2665 | } 2666 | if (isPresent(this._outlet)) { 2667 | throw new BaseException("Primary outlet is already registered."); 2668 | } 2669 | this._outlet = outlet; 2670 | if (isPresent(this.currentInstruction)) { 2671 | return this.commit(this.currentInstruction, false); 2672 | } 2673 | return _resolveToTrue; 2674 | }; 2675 | /** 2676 | * Unregister an outlet (because it was destroyed, etc). 2677 | * 2678 | * You probably don't need to use this unless you're writing a custom outlet implementation. 2679 | */ 2680 | Router.prototype.unregisterPrimaryOutlet = function (outlet) { 2681 | if (isPresent(outlet.name)) { 2682 | throw new BaseException("registerPrimaryOutlet expects to be called with an unnamed outlet."); 2683 | } 2684 | this._outlet = null; 2685 | }; 2686 | /** 2687 | * Register an outlet to notified of auxiliary route changes. 2688 | * 2689 | * You probably don't need to use this unless you're writing a reusable component. 2690 | */ 2691 | Router.prototype.registerAuxOutlet = function (outlet) { 2692 | var outletName = outlet.name; 2693 | if (isBlank(outletName)) { 2694 | throw new BaseException("registerAuxOutlet expects to be called with an outlet with a name."); 2695 | } 2696 | var router = this.auxRouter(this.hostComponent); 2697 | this._auxRouters.set(outletName, router); 2698 | router._outlet = outlet; 2699 | var auxInstruction; 2700 | if (isPresent(this.currentInstruction) && 2701 | isPresent(auxInstruction = this.currentInstruction.auxInstruction[outletName])) { 2702 | return router.commit(auxInstruction); 2703 | } 2704 | return _resolveToTrue; 2705 | }; 2706 | /** 2707 | * Given an instruction, returns `true` if the instruction is currently active, 2708 | * otherwise `false`. 2709 | */ 2710 | Router.prototype.isRouteActive = function (instruction) { 2711 | var _this = this; 2712 | var router = this; 2713 | if (isBlank(this.currentInstruction)) { 2714 | return false; 2715 | } 2716 | // `instruction` corresponds to the root router 2717 | while (isPresent(router.parent) && isPresent(instruction.child)) { 2718 | router = router.parent; 2719 | instruction = instruction.child; 2720 | } 2721 | if (isBlank(instruction.component) || isBlank(this.currentInstruction.component) || 2722 | this.currentInstruction.component.routeName != instruction.component.routeName) { 2723 | return false; 2724 | } 2725 | var paramEquals = true; 2726 | if (isPresent(this.currentInstruction.component.params)) { 2727 | StringMapWrapper.forEach(instruction.component.params, function (value, key) { 2728 | if (_this.currentInstruction.component.params[key] !== value) { 2729 | paramEquals = false; 2730 | } 2731 | }); 2732 | } 2733 | return paramEquals; 2734 | }; 2735 | /** 2736 | * Dynamically update the routing configuration and trigger a navigation. 2737 | * 2738 | * ### Usage 2739 | * 2740 | * ``` 2741 | * router.config([ 2742 | * { 'path': '/', 'component': IndexComp }, 2743 | * { 'path': '/user/:id', 'component': UserComp }, 2744 | * ]); 2745 | * ``` 2746 | */ 2747 | Router.prototype.config = function (definitions) { 2748 | var _this = this; 2749 | definitions.forEach(function (routeDefinition) { _this.registry.config(_this.hostComponent, routeDefinition); }); 2750 | return this.renavigate(); 2751 | }; 2752 | /** 2753 | * Navigate based on the provided Route Link DSL. It's preferred to navigate with this method 2754 | * over `navigateByUrl`. 2755 | * 2756 | * ### Usage 2757 | * 2758 | * This method takes an array representing the Route Link DSL: 2759 | * ``` 2760 | * ['./MyCmp', {param: 3}] 2761 | * ``` 2762 | * See the {@link RouterLink} directive for more. 2763 | */ 2764 | Router.prototype.navigate = function (linkParams) { 2765 | var instruction = this.generate(linkParams); 2766 | return this.navigateByInstruction(instruction, false); 2767 | }; 2768 | /** 2769 | * Navigate to a URL. Returns a promise that resolves when navigation is complete. 2770 | * It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle. 2771 | * 2772 | * If the given URL begins with a `/`, router will navigate absolutely. 2773 | * If the given URL does not begin with `/`, the router will navigate relative to this component. 2774 | */ 2775 | Router.prototype.navigateByUrl = function (url, _skipLocationChange) { 2776 | var _this = this; 2777 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 2778 | return this._currentNavigation = this._currentNavigation.then(function (_) { 2779 | _this.lastNavigationAttempt = url; 2780 | _this._startNavigating(); 2781 | return _this._afterPromiseFinishNavigating(_this.recognize(url).then(function (instruction) { 2782 | if (isBlank(instruction)) { 2783 | return false; 2784 | } 2785 | return _this._navigate(instruction, _skipLocationChange); 2786 | })); 2787 | }); 2788 | }; 2789 | /** 2790 | * Navigate via the provided instruction. Returns a promise that resolves when navigation is 2791 | * complete. 2792 | */ 2793 | Router.prototype.navigateByInstruction = function (instruction, _skipLocationChange) { 2794 | var _this = this; 2795 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 2796 | if (isBlank(instruction)) { 2797 | return _resolveToFalse; 2798 | } 2799 | return this._currentNavigation = this._currentNavigation.then(function (_) { 2800 | _this._startNavigating(); 2801 | return _this._afterPromiseFinishNavigating(_this._navigate(instruction, _skipLocationChange)); 2802 | }); 2803 | }; 2804 | /** @internal */ 2805 | Router.prototype._settleInstruction = function (instruction) { 2806 | var _this = this; 2807 | return instruction.resolveComponent().then(function (_) { 2808 | var unsettledInstructions = []; 2809 | if (isPresent(instruction.component)) { 2810 | instruction.component.reuse = false; 2811 | } 2812 | if (isPresent(instruction.child)) { 2813 | unsettledInstructions.push(_this._settleInstruction(instruction.child)); 2814 | } 2815 | StringMapWrapper.forEach(instruction.auxInstruction, function (instruction, _) { 2816 | unsettledInstructions.push(_this._settleInstruction(instruction)); 2817 | }); 2818 | return PromiseWrapper.all(unsettledInstructions); 2819 | }); 2820 | }; 2821 | /** @internal */ 2822 | Router.prototype._navigate = function (instruction, _skipLocationChange) { 2823 | var _this = this; 2824 | return this._settleInstruction(instruction) 2825 | .then(function (_) { return _this._routerCanReuse(instruction); }) 2826 | .then(function (_) { return _this._canActivate(instruction); }) 2827 | .then(function (result) { 2828 | if (!result) { 2829 | return false; 2830 | } 2831 | return _this._routerCanDeactivate(instruction).then(function (result) { 2832 | if (result) { 2833 | return _this.commit(instruction, _skipLocationChange).then(function (_) { 2834 | _this._emitNavigationFinish(instruction.toRootUrl()); 2835 | return true; 2836 | }); 2837 | } 2838 | }); 2839 | }); 2840 | }; 2841 | Router.prototype._emitNavigationFinish = function (url) { ObservableWrapper.callEmit(this._subject, url); }; 2842 | /** @internal */ 2843 | Router.prototype._emitNavigationFail = function (url) { ObservableWrapper.callError(this._subject, url); }; 2844 | Router.prototype._afterPromiseFinishNavigating = function (promise) { 2845 | var _this = this; 2846 | return PromiseWrapper.catchError(promise.then(function (_) { return _this._finishNavigating(); }), function (err) { 2847 | _this._finishNavigating(); 2848 | throw err; 2849 | }); 2850 | }; 2851 | /* 2852 | * Recursively set reuse flags 2853 | */ 2854 | /** @internal */ 2855 | Router.prototype._routerCanReuse = function (instruction) { 2856 | var _this = this; 2857 | if (isBlank(this._outlet)) { 2858 | return _resolveToFalse; 2859 | } 2860 | if (isBlank(instruction.component)) { 2861 | return _resolveToTrue; 2862 | } 2863 | return this._outlet.routerCanReuse(instruction.component).then(function (result) { 2864 | instruction.component.reuse = result; 2865 | if (result && isPresent(_this._childRouter) && isPresent(instruction.child)) { 2866 | return _this._childRouter._routerCanReuse(instruction.child); 2867 | } 2868 | }); 2869 | }; 2870 | Router.prototype._canActivate = function (nextInstruction) { 2871 | return canActivateOne(nextInstruction, this.currentInstruction); 2872 | }; 2873 | Router.prototype._routerCanDeactivate = function (instruction) { 2874 | var _this = this; 2875 | if (isBlank(this._outlet)) { 2876 | return _resolveToTrue; 2877 | } 2878 | var next; 2879 | var childInstruction = null; 2880 | var reuse = false; 2881 | var componentInstruction = null; 2882 | if (isPresent(instruction)) { 2883 | childInstruction = instruction.child; 2884 | componentInstruction = instruction.component; 2885 | reuse = isBlank(instruction.component) || instruction.component.reuse; 2886 | } 2887 | if (reuse) { 2888 | next = _resolveToTrue; 2889 | } 2890 | else { 2891 | next = this._outlet.routerCanDeactivate(componentInstruction); 2892 | } 2893 | // TODO: aux route lifecycle hooks 2894 | return next.then(function (result) { 2895 | if (result == false) { 2896 | return false; 2897 | } 2898 | if (isPresent(_this._childRouter)) { 2899 | // TODO: ideally, this closure would map to async-await in Dart. 2900 | // For now, casting to any to suppress an error. 2901 | return _this._childRouter._routerCanDeactivate(childInstruction); 2902 | } 2903 | return true; 2904 | }); 2905 | }; 2906 | /** 2907 | * Updates this router and all descendant routers according to the given instruction 2908 | */ 2909 | Router.prototype.commit = function (instruction, _skipLocationChange) { 2910 | var _this = this; 2911 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 2912 | this.currentInstruction = instruction; 2913 | var next = _resolveToTrue; 2914 | if (isPresent(this._outlet) && isPresent(instruction.component)) { 2915 | var componentInstruction = instruction.component; 2916 | if (componentInstruction.reuse) { 2917 | next = this._outlet.reuse(componentInstruction); 2918 | } 2919 | else { 2920 | next = 2921 | this.deactivate(instruction).then(function (_) { return _this._outlet.activate(componentInstruction); }); 2922 | } 2923 | if (isPresent(instruction.child)) { 2924 | next = next.then(function (_) { 2925 | if (isPresent(_this._childRouter)) { 2926 | return _this._childRouter.commit(instruction.child); 2927 | } 2928 | }); 2929 | } 2930 | } 2931 | var promises = []; 2932 | this._auxRouters.forEach(function (router, name) { 2933 | if (isPresent(instruction.auxInstruction[name])) { 2934 | promises.push(router.commit(instruction.auxInstruction[name])); 2935 | } 2936 | }); 2937 | return next.then(function (_) { return PromiseWrapper.all(promises); }); 2938 | }; 2939 | /** @internal */ 2940 | Router.prototype._startNavigating = function () { this.navigating = true; }; 2941 | /** @internal */ 2942 | Router.prototype._finishNavigating = function () { this.navigating = false; }; 2943 | /** 2944 | * Subscribe to URL updates from the router 2945 | */ 2946 | Router.prototype.subscribe = function (onNext, onError) { 2947 | return ObservableWrapper.subscribe(this._subject, onNext, onError); 2948 | }; 2949 | /** 2950 | * Removes the contents of this router's outlet and all descendant outlets 2951 | */ 2952 | Router.prototype.deactivate = function (instruction) { 2953 | var _this = this; 2954 | var childInstruction = null; 2955 | var componentInstruction = null; 2956 | if (isPresent(instruction)) { 2957 | childInstruction = instruction.child; 2958 | componentInstruction = instruction.component; 2959 | } 2960 | var next = _resolveToTrue; 2961 | if (isPresent(this._childRouter)) { 2962 | next = this._childRouter.deactivate(childInstruction); 2963 | } 2964 | if (isPresent(this._outlet)) { 2965 | next = next.then(function (_) { return _this._outlet.deactivate(componentInstruction); }); 2966 | } 2967 | // TODO: handle aux routes 2968 | return next; 2969 | }; 2970 | /** 2971 | * Given a URL, returns an instruction representing the component graph 2972 | */ 2973 | Router.prototype.recognize = function (url) { 2974 | var ancestorComponents = this._getAncestorInstructions(); 2975 | return this.registry.recognize(url, ancestorComponents); 2976 | }; 2977 | Router.prototype._getAncestorInstructions = function () { 2978 | var ancestorInstructions = [this.currentInstruction]; 2979 | var ancestorRouter = this; 2980 | while (isPresent(ancestorRouter = ancestorRouter.parent)) { 2981 | ancestorInstructions.unshift(ancestorRouter.currentInstruction); 2982 | } 2983 | return ancestorInstructions; 2984 | }; 2985 | /** 2986 | * Navigates to either the last URL successfully navigated to, or the last URL requested if the 2987 | * router has yet to successfully navigate. 2988 | */ 2989 | Router.prototype.renavigate = function () { 2990 | if (isBlank(this.lastNavigationAttempt)) { 2991 | return this._currentNavigation; 2992 | } 2993 | return this.navigateByUrl(this.lastNavigationAttempt); 2994 | }; 2995 | /** 2996 | * Generate an `Instruction` based on the provided Route Link DSL. 2997 | */ 2998 | Router.prototype.generate = function (linkParams) { 2999 | var ancestorInstructions = this._getAncestorInstructions(); 3000 | return this.registry.generate(linkParams, ancestorInstructions); 3001 | }; 3002 | return Router; 3003 | })(); 3004 | exports.Router = Router; 3005 | var RootRouter = (function (_super) { 3006 | __extends(RootRouter, _super); 3007 | function RootRouter(registry, location, primaryComponent) { 3008 | var _this = this; 3009 | _super.call(this, registry, null, primaryComponent); 3010 | this.root = this; 3011 | this._location = location; 3012 | this._locationSub = this._location.subscribe(function (change) { 3013 | // we call recognize ourselves 3014 | _this.recognize(change['url']).then(function (instruction) { 3015 | if (isPresent(instruction)) { 3016 | _this.navigateByInstruction(instruction, isPresent(change['pop'])).then(function (_) { 3017 | // this is a popstate event; no need to change the URL 3018 | if (isPresent(change['pop']) && change['type'] != 'hashchange') { 3019 | return; 3020 | } 3021 | var emitPath = instruction.toUrlPath(); 3022 | var emitQuery = instruction.toUrlQuery(); 3023 | if (emitPath.length > 0 && emitPath[0] != '/') { 3024 | emitPath = '/' + emitPath; 3025 | } 3026 | // We've opted to use pushstate and popState APIs regardless of whether you 3027 | // an app uses HashLocationStrategy or PathLocationStrategy. 3028 | // However, apps that are migrating might have hash links that operate outside 3029 | // angular to which routing must respond. 3030 | // Therefore we know that all hashchange events occur outside Angular. 3031 | // To support these cases where we respond to hashchanges and redirect as a 3032 | // result, we need to replace the top item on the stack. 3033 | if (change['type'] == 'hashchange') { 3034 | if (instruction.toRootUrl() != _this._location.path()) { 3035 | _this._location.replaceState(emitPath, emitQuery); 3036 | } 3037 | } 3038 | else { 3039 | _this._location.go(emitPath, emitQuery); 3040 | } 3041 | }); 3042 | } 3043 | else { 3044 | _this._emitNavigationFail(change['url']); 3045 | } 3046 | }); 3047 | }); 3048 | this.registry.configFromComponent(primaryComponent); 3049 | this.navigateByUrl(location.path()); 3050 | } 3051 | RootRouter.prototype.commit = function (instruction, _skipLocationChange) { 3052 | var _this = this; 3053 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 3054 | var emitPath = instruction.toUrlPath(); 3055 | var emitQuery = instruction.toUrlQuery(); 3056 | if (emitPath.length > 0 && emitPath[0] != '/') { 3057 | emitPath = '/' + emitPath; 3058 | } 3059 | var promise = _super.prototype.commit.call(this, instruction); 3060 | if (!_skipLocationChange) { 3061 | promise = promise.then(function (_) { _this._location.go(emitPath, emitQuery); }); 3062 | } 3063 | return promise; 3064 | }; 3065 | RootRouter.prototype.dispose = function () { 3066 | if (isPresent(this._locationSub)) { 3067 | ObservableWrapper.dispose(this._locationSub); 3068 | this._locationSub = null; 3069 | } 3070 | }; 3071 | return RootRouter; 3072 | })(Router); 3073 | exports.RootRouter = RootRouter; 3074 | var ChildRouter = (function (_super) { 3075 | __extends(ChildRouter, _super); 3076 | function ChildRouter(parent, hostComponent) { 3077 | _super.call(this, parent.registry, parent, hostComponent, parent.root); 3078 | this.parent = parent; 3079 | } 3080 | ChildRouter.prototype.navigateByUrl = function (url, _skipLocationChange) { 3081 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 3082 | // Delegate navigation to the root router 3083 | return this.parent.navigateByUrl(url, _skipLocationChange); 3084 | }; 3085 | ChildRouter.prototype.navigateByInstruction = function (instruction, _skipLocationChange) { 3086 | if (_skipLocationChange === void 0) { _skipLocationChange = false; } 3087 | // Delegate navigation to the root router 3088 | return this.parent.navigateByInstruction(instruction, _skipLocationChange); 3089 | }; 3090 | return ChildRouter; 3091 | })(Router); 3092 | function canActivateOne(nextInstruction, prevInstruction) { 3093 | var next = _resolveToTrue; 3094 | if (isBlank(nextInstruction.component)) { 3095 | return next; 3096 | } 3097 | if (isPresent(nextInstruction.child)) { 3098 | next = canActivateOne(nextInstruction.child, isPresent(prevInstruction) ? prevInstruction.child : null); 3099 | } 3100 | return next.then(function (result) { 3101 | if (result == false) { 3102 | return false; 3103 | } 3104 | if (nextInstruction.component.reuse) { 3105 | return true; 3106 | } 3107 | var hook = route_lifecycle_reflector_1.getCanActivateHook(nextInstruction.component.componentType); 3108 | if (isPresent(hook)) { 3109 | return hook(nextInstruction.component, isPresent(prevInstruction) ? prevInstruction.component : null); 3110 | } 3111 | return true; 3112 | }); 3113 | } 3114 | 3115 | 3116 | function getComponentConstructor(name) { 3117 | var serviceName = name + 'Directive'; 3118 | if ($injector.has(serviceName)) { 3119 | var definitions = $injector.get(serviceName); 3120 | if (definitions.length > 1) { 3121 | throw new BaseException('too many directives named "' + name + '"'); 3122 | } 3123 | return definitions[0].controller; 3124 | } else { 3125 | throw new BaseException('directive "' + name + '" is not registered'); 3126 | } 3127 | } 3128 | 3129 | //TODO: this is a hack to replace the exiting implementation at run-time 3130 | exports.getCanActivateHook = function (directiveName) { 3131 | var controller = getComponentConstructor(directiveName); 3132 | return controller.$canActivate && function (next, prev) { 3133 | return $injector.invoke(controller.$canActivate, null, { 3134 | $nextInstruction: next, 3135 | $prevInstruction: prev 3136 | }); 3137 | }; 3138 | }; 3139 | 3140 | // This hack removes assertions about the type of the "component" 3141 | // property in a route config 3142 | exports.assertComponentExists = function () {}; 3143 | 3144 | angular.stringifyInstruction = function (instruction) { 3145 | return instruction.toRootUrl(); 3146 | }; 3147 | 3148 | var RouteRegistry = exports.RouteRegistry; 3149 | var RootRouter = exports.RootRouter; 3150 | 3151 | // Override this method to actually get hold of the child routes 3152 | RouteRegistry.prototype.configFromComponent = function (component) { 3153 | var that = this; 3154 | if (isString(component)) { 3155 | // Don't read the annotations component a type more than once – 3156 | // this prevents an infinite loop if a component routes recursively. 3157 | if (this._rules.has(component)) { 3158 | return; 3159 | } 3160 | var controller = getComponentConstructor(component); 3161 | if (angular.isArray(controller.$routeConfig)) { 3162 | controller.$routeConfig.forEach(function (config) { 3163 | var loader = config.loader; 3164 | if (isPresent(loader)) { 3165 | config = angular.extend({}, config, { loader: $injector.invoke(loader) }); 3166 | } 3167 | that.config(component, config); 3168 | }); 3169 | } 3170 | } 3171 | 3172 | } 3173 | 3174 | var registry = new RouteRegistry($routerRootComponent); 3175 | var location = new Location(); 3176 | 3177 | var router = new RootRouter(registry, location, $routerRootComponent); 3178 | $rootScope.$watch(function () { return $location.url(); }, function (path) { 3179 | if (router.lastNavigationAttempt !== path) { 3180 | router.navigateByUrl(path); 3181 | } 3182 | }); 3183 | 3184 | router.subscribe(function () { 3185 | $rootScope.$broadcast('$routeChangeSuccess', {}); 3186 | }); 3187 | 3188 | return router; 3189 | } 3190 | 3191 | }()); 3192 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by doguhanuluca on 1/21/16. 3 | */ 4 | 'use strict'; 5 | 6 | require('./angular_1_router') 7 | 8 | module.exports = 'ngComponentRouter' -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngcomponentrouter", 3 | "version": "2.1.0", 4 | "description": "Angular 2 Component Router for Angular 1", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://duluca@github.com/excellalabs/ngComponentRouter.git" 12 | }, 13 | "keywords": [ 14 | "ng", 15 | "angular", 16 | "component", 17 | "router" 18 | ], 19 | "author": "angular-core-team", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/excellalabs/ngComponentRouter/issues" 23 | }, 24 | "homepage": "https://github.com/excellalabs/ngComponentRouter#readme" 25 | } 26 | --------------------------------------------------------------------------------