├── component.json ├── README.md ├── composer.json ├── LICENSE ├── angular-route.min.js └── angular-route.js /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "repo": "components/angular-route", 4 | "version": "1.2.0", 5 | "main": "angular-route.js", 6 | "scripts": [ 7 | "angular-route.js" 8 | ], 9 | "files": [ 10 | "angular-route.min.js" 11 | ], 12 | "dependencies": { 13 | "components/angular.js": "1.2.0" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS ngRoute 2 | ================= 3 | 4 | Shim repository for the [angular-route](http://docs.angularjs.org/api/ngRoute) module. 5 | 6 | Package Managers 7 | ---------------- 8 | 9 | * [Component](https://github.com/component/component): `components/angular-route` 10 | * [Composer](http://packagist.org/packages/components/angular-route): `components/angular-route` 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components/angular-route", 3 | "description": "Routing module for AngularJS", 4 | "type": "component", 5 | "homepage": "http://angularjs.org", 6 | "license": "MIT", 7 | "require": { 8 | "components/angular.js": "1.2.0" 9 | }, 10 | "extra": { 11 | "component": { 12 | "name": "angular-route", 13 | "scripts": [ 14 | "angular-route.js" 15 | ], 16 | "files": [ 17 | "angular-route.min.js" 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Components 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.0 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(t,c,B){'use strict';function w(s,r,g,a,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",compile:function(k,d,A){return function(u,k,d){function v(){l&&(l.$destroy(),l=null);m&&(h.leave(m),m=null)}function x(){var f=s.current&&s.current.locals,y=f&&f.$template;if(y){var z=u.$new();A(z,function(e){e.html(y);h.enter(e,null,m||k,function(){!c.isDefined(n)||n&&!u.$eval(n)||r()});v();var p=g(e.contents()),q=s.current;l=q.scope=z;m=e;if(q.controller){f.$scope=l;var d=a(q.controller, 7 | f);q.controllerAs&&(l[q.controllerAs]=d);e.data("$ngControllerController",d);e.children().data("$ngControllerController",d)}p(l);l.$emit("$viewContentLoaded");l.$eval(b)})}else v()}var l,m,n=d.autoscroll,b=d.onload||"";u.$on("$routeChangeSuccess",x);x()}}}}t=c.module("ngRoute",["ng"]).provider("$route",function(){function s(a,h){return c.extend(new (c.extend(function(){},{prototype:a})),h)}function r(a,c){var k=c.caseInsensitiveMatch,d={originalPath:a,regexp:a},g=d.keys=[];a=a.replace(/([().])/g, 8 | "\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,c,h,d){a="?"===d?d:null;d="*"===d?d:null;g.push({name:h,optional:!!a});c=c||"";return""+(a?"":c)+"(?:"+(a?c:"")+(d&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=RegExp("^"+a+"$",k?"i":"");return d}var g={};this.when=function(a,h){g[a]=c.extend({reloadOnSearch:!0},h,a&&r(a,h));if(a){var k="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[k]=c.extend({redirectTo:a},r(k,h))}return this};this.otherwise=function(a){this.when(null, 9 | a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,h,k,d,r,u,t,w){function v(){var b=x(),f=n.current;if(b&&f&&b.$$route===f.$$route&&c.equals(b.pathParams,f.pathParams)&&!b.reloadOnSearch&&!m)f.params=b.params,c.copy(f.params,k),a.$broadcast("$routeUpdate",f);else if(b||f)m=!1,a.$broadcast("$routeChangeStart",b,f),(n.current=b)&&b.redirectTo&&(c.isString(b.redirectTo)?h.path(l(b.redirectTo,b.params)).search(b.params).replace(): 10 | h.url(b.redirectTo(b.pathParams,h.path(),h.search())).replace()),d.when(b).then(function(){if(b){var a=c.extend({},b.resolve),f,e;c.forEach(a,function(b,f){a[f]=c.isString(b)?r.get(b):r.invoke(b)});c.isDefined(f=b.template)?c.isFunction(f)&&(f=f(b.params)):c.isDefined(e=b.templateUrl)&&(c.isFunction(e)&&(e=e(b.params)),e=w.getTrustedResourceUrl(e),c.isDefined(e)&&(b.loadedTemplateUrl=e,f=u.get(e,{cache:t}).then(function(b){return b.data})));c.isDefined(f)&&(a.$template=f);return d.all(a)}}).then(function(d){b== 11 | n.current&&(b&&(b.locals=d,c.copy(b.params,k)),a.$broadcast("$routeChangeSuccess",b,f))},function(c){b==n.current&&a.$broadcast("$routeChangeError",b,f,c)})}function x(){var b,a;c.forEach(g,function(d,l){var e;if(e=!a){var p=h.path();e=d.keys;var q={};if(d.regexp)if(p=d.regexp.exec(p)){for(var g=1,k=p.length;g 20 | */ 21 | /* global -ngRouteModule */ 22 | var ngRouteModule = angular.module('ngRoute', ['ng']). 23 | provider('$route', $RouteProvider); 24 | 25 | /** 26 | * @ngdoc object 27 | * @name ngRoute.$routeProvider 28 | * @function 29 | * 30 | * @description 31 | * 32 | * Used for configuring routes. See {@link ngRoute.$route $route} for an example. 33 | * 34 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 35 | */ 36 | function $RouteProvider(){ 37 | function inherit(parent, extra) { 38 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 39 | } 40 | 41 | var routes = {}; 42 | 43 | /** 44 | * @ngdoc method 45 | * @name ngRoute.$routeProvider#when 46 | * @methodOf ngRoute.$routeProvider 47 | * 48 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 49 | * contains redundant trailing slash or is missing one, the route will still match and the 50 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 51 | * route definition. 52 | * 53 | * * `path` can contain named groups starting with a colon (`:name`). All characters up 54 | * to the next slash are matched and stored in `$routeParams` under the given `name` 55 | * when the route matches. 56 | * * `path` can contain named groups starting with a colon and ending with a star (`:name*`). 57 | * All characters are eagerly stored in `$routeParams` under the given `name` 58 | * when the route matches. 59 | * * `path` can contain optional named groups with a question mark (`:name?`). 60 | * 61 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 62 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 63 | * 64 | * * `color: brown` 65 | * * `largecode: code/with/slashs`. 66 | * 67 | * 68 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 69 | * match. 70 | * 71 | * Object properties: 72 | * 73 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 74 | * newly created scope or the name of a {@link angular.Module#controller registered 75 | * controller} if passed as a string. 76 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 77 | * published to scope under the `controllerAs` name. 78 | * - `template` – `{string=|function()=}` – html template as a string or a function that 79 | * returns an html template as a string which should be used by {@link 80 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 81 | * This property takes precedence over `templateUrl`. 82 | * 83 | * If `template` is a function, it will be called with the following parameters: 84 | * 85 | * - `{Array.}` - route parameters extracted from the current 86 | * `$location.path()` by applying the current route 87 | * 88 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 89 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 90 | * 91 | * If `templateUrl` is a function, it will be called with the following parameters: 92 | * 93 | * - `{Array.}` - route parameters extracted from the current 94 | * `$location.path()` by applying the current route 95 | * 96 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 97 | * be injected into the controller. If any of these dependencies are promises, the router 98 | * will wait for them all to be resolved or one to be rejected before the controller is 99 | * instantiated. 100 | * If all the promises are resolved successfully, the values of the resolved promises are 101 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 102 | * fired. If any of the promises are rejected the 103 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 104 | * is: 105 | * 106 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 107 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 108 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 109 | * and the return value is treated as the dependency. If the result is a promise, it is 110 | * resolved before its value is injected into the controller. Be aware that 111 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 112 | * functions. Use `$route.current.params` to access the new route parameters, instead. 113 | * 114 | * - `redirectTo` – {(string|function())=} – value to update 115 | * {@link ng.$location $location} path with and trigger route redirection. 116 | * 117 | * If `redirectTo` is a function, it will be called with the following parameters: 118 | * 119 | * - `{Object.}` - route parameters extracted from the current 120 | * `$location.path()` by applying the current route templateUrl. 121 | * - `{string}` - current `$location.path()` 122 | * - `{Object}` - current `$location.search()` 123 | * 124 | * The custom `redirectTo` function is expected to return a string which will be used 125 | * to update `$location.path()` and `$location.search()`. 126 | * 127 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 128 | * or `$location.hash()` changes. 129 | * 130 | * If the option is set to `false` and url in the browser changes, then 131 | * `$routeUpdate` event is broadcasted on the root scope. 132 | * 133 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 134 | * 135 | * If the option is set to `true`, then the particular route can be matched without being 136 | * case sensitive 137 | * 138 | * @returns {Object} self 139 | * 140 | * @description 141 | * Adds a new route definition to the `$route` service. 142 | */ 143 | this.when = function(path, route) { 144 | routes[path] = angular.extend( 145 | {reloadOnSearch: true}, 146 | route, 147 | path && pathRegExp(path, route) 148 | ); 149 | 150 | // create redirection for trailing slashes 151 | if (path) { 152 | var redirectPath = (path[path.length-1] == '/') 153 | ? path.substr(0, path.length-1) 154 | : path +'/'; 155 | 156 | routes[redirectPath] = angular.extend( 157 | {redirectTo: path}, 158 | pathRegExp(redirectPath, route) 159 | ); 160 | } 161 | 162 | return this; 163 | }; 164 | 165 | /** 166 | * @param path {string} path 167 | * @param opts {Object} options 168 | * @return {?Object} 169 | * 170 | * @description 171 | * Normalizes the given path, returning a regular expression 172 | * and the original path. 173 | * 174 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 175 | */ 176 | function pathRegExp(path, opts) { 177 | var insensitive = opts.caseInsensitiveMatch, 178 | ret = { 179 | originalPath: path, 180 | regexp: path 181 | }, 182 | keys = ret.keys = []; 183 | 184 | path = path 185 | .replace(/([().])/g, '\\$1') 186 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 187 | var optional = option === '?' ? option : null; 188 | var star = option === '*' ? option : null; 189 | keys.push({ name: key, optional: !!optional }); 190 | slash = slash || ''; 191 | return '' 192 | + (optional ? '' : slash) 193 | + '(?:' 194 | + (optional ? slash : '') 195 | + (star && '(.+?)' || '([^/]+)') 196 | + (optional || '') 197 | + ')' 198 | + (optional || ''); 199 | }) 200 | .replace(/([\/$\*])/g, '\\$1'); 201 | 202 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 203 | return ret; 204 | } 205 | 206 | /** 207 | * @ngdoc method 208 | * @name ngRoute.$routeProvider#otherwise 209 | * @methodOf ngRoute.$routeProvider 210 | * 211 | * @description 212 | * Sets route definition that will be used on route change when no other route definition 213 | * is matched. 214 | * 215 | * @param {Object} params Mapping information to be assigned to `$route.current`. 216 | * @returns {Object} self 217 | */ 218 | this.otherwise = function(params) { 219 | this.when(null, params); 220 | return this; 221 | }; 222 | 223 | 224 | this.$get = ['$rootScope', 225 | '$location', 226 | '$routeParams', 227 | '$q', 228 | '$injector', 229 | '$http', 230 | '$templateCache', 231 | '$sce', 232 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 233 | 234 | /** 235 | * @ngdoc object 236 | * @name ngRoute.$route 237 | * @requires $location 238 | * @requires $routeParams 239 | * 240 | * @property {Object} current Reference to the current route definition. 241 | * The route definition contains: 242 | * 243 | * - `controller`: The controller constructor as define in route definition. 244 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 245 | * controller instantiation. The `locals` contain 246 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 247 | * 248 | * - `$scope` - The current route scope. 249 | * - `$template` - The current route template HTML. 250 | * 251 | * @property {Array.} routes Array of all configured routes. 252 | * 253 | * @description 254 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 255 | * It watches `$location.url()` and tries to map the path to an existing route definition. 256 | * 257 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 258 | * 259 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 260 | * 261 | * The `$route` service is typically used in conjunction with the 262 | * {@link ngRoute.directive:ngView `ngView`} directive and the 263 | * {@link ngRoute.$routeParams `$routeParams`} service. 264 | * 265 | * @example 266 | This example shows how changing the URL hash causes the `$route` to match a route against the 267 | URL, and the `ngView` pulls in the partial. 268 | 269 | Note that this example is using {@link ng.directive:script inlined templates} 270 | to get it working on jsfiddle as well. 271 | 272 | 273 | 274 |
275 | Choose: 276 | Moby | 277 | Moby: Ch1 | 278 | Gatsby | 279 | Gatsby: Ch4 | 280 | Scarlet Letter
281 | 282 |
283 |
284 | 285 |
$location.path() = {{$location.path()}}
286 |
$route.current.templateUrl = {{$route.current.templateUrl}}
287 |
$route.current.params = {{$route.current.params}}
288 |
$route.current.scope.name = {{$route.current.scope.name}}
289 |
$routeParams = {{$routeParams}}
290 |
291 |
292 | 293 | 294 | controller: {{name}}
295 | Book Id: {{params.bookId}}
296 |
297 | 298 | 299 | controller: {{name}}
300 | Book Id: {{params.bookId}}
301 | Chapter Id: {{params.chapterId}} 302 |
303 | 304 | 305 | angular.module('ngViewExample', ['ngRoute']) 306 | 307 | .config(function($routeProvider, $locationProvider) { 308 | $routeProvider.when('/Book/:bookId', { 309 | templateUrl: 'book.html', 310 | controller: BookCntl, 311 | resolve: { 312 | // I will cause a 1 second delay 313 | delay: function($q, $timeout) { 314 | var delay = $q.defer(); 315 | $timeout(delay.resolve, 1000); 316 | return delay.promise; 317 | } 318 | } 319 | }); 320 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 321 | templateUrl: 'chapter.html', 322 | controller: ChapterCntl 323 | }); 324 | 325 | // configure html5 to get links working on jsfiddle 326 | $locationProvider.html5Mode(true); 327 | }); 328 | 329 | function MainCntl($scope, $route, $routeParams, $location) { 330 | $scope.$route = $route; 331 | $scope.$location = $location; 332 | $scope.$routeParams = $routeParams; 333 | } 334 | 335 | function BookCntl($scope, $routeParams) { 336 | $scope.name = "BookCntl"; 337 | $scope.params = $routeParams; 338 | } 339 | 340 | function ChapterCntl($scope, $routeParams) { 341 | $scope.name = "ChapterCntl"; 342 | $scope.params = $routeParams; 343 | } 344 | 345 | 346 | 347 | it('should load and compile correct template', function() { 348 | element('a:contains("Moby: Ch1")').click(); 349 | var content = element('.doc-example-live [ng-view]').text(); 350 | expect(content).toMatch(/controller\: ChapterCntl/); 351 | expect(content).toMatch(/Book Id\: Moby/); 352 | expect(content).toMatch(/Chapter Id\: 1/); 353 | 354 | element('a:contains("Scarlet")').click(); 355 | sleep(2); // promises are not part of scenario waiting 356 | content = element('.doc-example-live [ng-view]').text(); 357 | expect(content).toMatch(/controller\: BookCntl/); 358 | expect(content).toMatch(/Book Id\: Scarlet/); 359 | }); 360 | 361 |
362 | */ 363 | 364 | /** 365 | * @ngdoc event 366 | * @name ngRoute.$route#$routeChangeStart 367 | * @eventOf ngRoute.$route 368 | * @eventType broadcast on root scope 369 | * @description 370 | * Broadcasted before a route change. At this point the route services starts 371 | * resolving all of the dependencies needed for the route change to occurs. 372 | * Typically this involves fetching the view template as well as any dependencies 373 | * defined in `resolve` route property. Once all of the dependencies are resolved 374 | * `$routeChangeSuccess` is fired. 375 | * 376 | * @param {Object} angularEvent Synthetic event object. 377 | * @param {Route} next Future route information. 378 | * @param {Route} current Current route information. 379 | */ 380 | 381 | /** 382 | * @ngdoc event 383 | * @name ngRoute.$route#$routeChangeSuccess 384 | * @eventOf ngRoute.$route 385 | * @eventType broadcast on root scope 386 | * @description 387 | * Broadcasted after a route dependencies are resolved. 388 | * {@link ngRoute.directive:ngView ngView} listens for the directive 389 | * to instantiate the controller and render the view. 390 | * 391 | * @param {Object} angularEvent Synthetic event object. 392 | * @param {Route} current Current route information. 393 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 394 | * first route entered. 395 | */ 396 | 397 | /** 398 | * @ngdoc event 399 | * @name ngRoute.$route#$routeChangeError 400 | * @eventOf ngRoute.$route 401 | * @eventType broadcast on root scope 402 | * @description 403 | * Broadcasted if any of the resolve promises are rejected. 404 | * 405 | * @param {Object} angularEvent Synthetic event object 406 | * @param {Route} current Current route information. 407 | * @param {Route} previous Previous route information. 408 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 409 | */ 410 | 411 | /** 412 | * @ngdoc event 413 | * @name ngRoute.$route#$routeUpdate 414 | * @eventOf ngRoute.$route 415 | * @eventType broadcast on root scope 416 | * @description 417 | * 418 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 419 | * instance of the Controller. 420 | */ 421 | 422 | var forceReload = false, 423 | $route = { 424 | routes: routes, 425 | 426 | /** 427 | * @ngdoc method 428 | * @name ngRoute.$route#reload 429 | * @methodOf ngRoute.$route 430 | * 431 | * @description 432 | * Causes `$route` service to reload the current route even if 433 | * {@link ng.$location $location} hasn't changed. 434 | * 435 | * As a result of that, {@link ngRoute.directive:ngView ngView} 436 | * creates new scope, reinstantiates the controller. 437 | */ 438 | reload: function() { 439 | forceReload = true; 440 | $rootScope.$evalAsync(updateRoute); 441 | } 442 | }; 443 | 444 | $rootScope.$on('$locationChangeSuccess', updateRoute); 445 | 446 | return $route; 447 | 448 | ///////////////////////////////////////////////////// 449 | 450 | /** 451 | * @param on {string} current url 452 | * @param route {Object} route regexp to match the url against 453 | * @return {?Object} 454 | * 455 | * @description 456 | * Check if the route matches the current url. 457 | * 458 | * Inspired by match in 459 | * visionmedia/express/lib/router/router.js. 460 | */ 461 | function switchRouteMatcher(on, route) { 462 | var keys = route.keys, 463 | params = {}; 464 | 465 | if (!route.regexp) return null; 466 | 467 | var m = route.regexp.exec(on); 468 | if (!m) return null; 469 | 470 | for (var i = 1, len = m.length; i < len; ++i) { 471 | var key = keys[i - 1]; 472 | 473 | var val = 'string' == typeof m[i] 474 | ? decodeURIComponent(m[i]) 475 | : m[i]; 476 | 477 | if (key && val) { 478 | params[key.name] = val; 479 | } 480 | } 481 | return params; 482 | } 483 | 484 | function updateRoute() { 485 | var next = parseRoute(), 486 | last = $route.current; 487 | 488 | if (next && last && next.$$route === last.$$route 489 | && angular.equals(next.pathParams, last.pathParams) 490 | && !next.reloadOnSearch && !forceReload) { 491 | last.params = next.params; 492 | angular.copy(last.params, $routeParams); 493 | $rootScope.$broadcast('$routeUpdate', last); 494 | } else if (next || last) { 495 | forceReload = false; 496 | $rootScope.$broadcast('$routeChangeStart', next, last); 497 | $route.current = next; 498 | if (next) { 499 | if (next.redirectTo) { 500 | if (angular.isString(next.redirectTo)) { 501 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 502 | .replace(); 503 | } else { 504 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 505 | .replace(); 506 | } 507 | } 508 | } 509 | 510 | $q.when(next). 511 | then(function() { 512 | if (next) { 513 | var locals = angular.extend({}, next.resolve), 514 | template, templateUrl; 515 | 516 | angular.forEach(locals, function(value, key) { 517 | locals[key] = angular.isString(value) ? 518 | $injector.get(value) : $injector.invoke(value); 519 | }); 520 | 521 | if (angular.isDefined(template = next.template)) { 522 | if (angular.isFunction(template)) { 523 | template = template(next.params); 524 | } 525 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 526 | if (angular.isFunction(templateUrl)) { 527 | templateUrl = templateUrl(next.params); 528 | } 529 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 530 | if (angular.isDefined(templateUrl)) { 531 | next.loadedTemplateUrl = templateUrl; 532 | template = $http.get(templateUrl, {cache: $templateCache}). 533 | then(function(response) { return response.data; }); 534 | } 535 | } 536 | if (angular.isDefined(template)) { 537 | locals['$template'] = template; 538 | } 539 | return $q.all(locals); 540 | } 541 | }). 542 | // after route change 543 | then(function(locals) { 544 | if (next == $route.current) { 545 | if (next) { 546 | next.locals = locals; 547 | angular.copy(next.params, $routeParams); 548 | } 549 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 550 | } 551 | }, function(error) { 552 | if (next == $route.current) { 553 | $rootScope.$broadcast('$routeChangeError', next, last, error); 554 | } 555 | }); 556 | } 557 | } 558 | 559 | 560 | /** 561 | * @returns the current active route, by matching it against the URL 562 | */ 563 | function parseRoute() { 564 | // Match a route 565 | var params, match; 566 | angular.forEach(routes, function(route, path) { 567 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 568 | match = inherit(route, { 569 | params: angular.extend({}, $location.search(), params), 570 | pathParams: params}); 571 | match.$$route = route; 572 | } 573 | }); 574 | // No route matched; fallback to "otherwise" route 575 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 576 | } 577 | 578 | /** 579 | * @returns interpolation of the redirect path with the parameters 580 | */ 581 | function interpolate(string, params) { 582 | var result = []; 583 | angular.forEach((string||'').split(':'), function(segment, i) { 584 | if (i === 0) { 585 | result.push(segment); 586 | } else { 587 | var segmentMatch = segment.match(/(\w+)(.*)/); 588 | var key = segmentMatch[1]; 589 | result.push(params[key]); 590 | result.push(segmentMatch[2] || ''); 591 | delete params[key]; 592 | } 593 | }); 594 | return result.join(''); 595 | } 596 | }]; 597 | } 598 | 599 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 600 | 601 | 602 | /** 603 | * @ngdoc object 604 | * @name ngRoute.$routeParams 605 | * @requires $route 606 | * 607 | * @description 608 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 609 | * 610 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 611 | * 612 | * The route parameters are a combination of {@link ng.$location `$location`}'s 613 | * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. 614 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 615 | * 616 | * In case of parameter name collision, `path` params take precedence over `search` params. 617 | * 618 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 619 | * (but its properties will likely change) even when a route change occurs. 620 | * 621 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 622 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 623 | * Instead you can use `$route.current.params` to access the new route's parameters. 624 | * 625 | * @example 626 | *
627 |  *  // Given:
628 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
629 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
630 |  *  //
631 |  *  // Then
632 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
633 |  * 
634 | */ 635 | function $RouteParamsProvider() { 636 | this.$get = function() { return {}; }; 637 | } 638 | 639 | ngRouteModule.directive('ngView', ngViewFactory); 640 | 641 | /** 642 | * @ngdoc directive 643 | * @name ngRoute.directive:ngView 644 | * @restrict ECA 645 | * 646 | * @description 647 | * # Overview 648 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 649 | * including the rendered template of the current route into the main layout (`index.html`) file. 650 | * Every time the current route changes, the included view changes with it according to the 651 | * configuration of the `$route` service. 652 | * 653 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 654 | * 655 | * @animations 656 | * enter - animation is used to bring new content into the browser. 657 | * leave - animation is used to animate existing content away. 658 | * 659 | * The enter and leave animation occur concurrently. 660 | * 661 | * @scope 662 | * @priority 400 663 | * @example 664 | 665 | 666 |
667 | Choose: 668 | Moby | 669 | Moby: Ch1 | 670 | Gatsby | 671 | Gatsby: Ch4 | 672 | Scarlet Letter
673 | 674 |
675 |
676 |
677 |
678 | 679 |
$location.path() = {{main.$location.path()}}
680 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
681 |
$route.current.params = {{main.$route.current.params}}
682 |
$route.current.scope.name = {{main.$route.current.scope.name}}
683 |
$routeParams = {{main.$routeParams}}
684 |
685 |
686 | 687 | 688 |
689 | controller: {{book.name}}
690 | Book Id: {{book.params.bookId}}
691 |
692 |
693 | 694 | 695 |
696 | controller: {{chapter.name}}
697 | Book Id: {{chapter.params.bookId}}
698 | Chapter Id: {{chapter.params.chapterId}} 699 |
700 |
701 | 702 | 703 | .view-animate-container { 704 | position:relative; 705 | height:100px!important; 706 | position:relative; 707 | background:white; 708 | border:1px solid black; 709 | height:40px; 710 | overflow:hidden; 711 | } 712 | 713 | .view-animate { 714 | padding:10px; 715 | } 716 | 717 | .view-animate.ng-enter, .view-animate.ng-leave { 718 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 719 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 720 | 721 | display:block; 722 | width:100%; 723 | border-left:1px solid black; 724 | 725 | position:absolute; 726 | top:0; 727 | left:0; 728 | right:0; 729 | bottom:0; 730 | padding:10px; 731 | } 732 | 733 | .view-animate.ng-enter { 734 | left:100%; 735 | } 736 | .view-animate.ng-enter.ng-enter-active { 737 | left:0; 738 | } 739 | .view-animate.ng-leave.ng-leave-active { 740 | left:-100%; 741 | } 742 | 743 | 744 | 745 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], 746 | function($routeProvider, $locationProvider) { 747 | $routeProvider.when('/Book/:bookId', { 748 | templateUrl: 'book.html', 749 | controller: BookCntl, 750 | controllerAs: 'book' 751 | }); 752 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 753 | templateUrl: 'chapter.html', 754 | controller: ChapterCntl, 755 | controllerAs: 'chapter' 756 | }); 757 | 758 | // configure html5 to get links working on jsfiddle 759 | $locationProvider.html5Mode(true); 760 | }); 761 | 762 | function MainCntl($route, $routeParams, $location) { 763 | this.$route = $route; 764 | this.$location = $location; 765 | this.$routeParams = $routeParams; 766 | } 767 | 768 | function BookCntl($routeParams) { 769 | this.name = "BookCntl"; 770 | this.params = $routeParams; 771 | } 772 | 773 | function ChapterCntl($routeParams) { 774 | this.name = "ChapterCntl"; 775 | this.params = $routeParams; 776 | } 777 | 778 | 779 | 780 | it('should load and compile correct template', function() { 781 | element('a:contains("Moby: Ch1")').click(); 782 | var content = element('.doc-example-live [ng-view]').text(); 783 | expect(content).toMatch(/controller\: ChapterCntl/); 784 | expect(content).toMatch(/Book Id\: Moby/); 785 | expect(content).toMatch(/Chapter Id\: 1/); 786 | 787 | element('a:contains("Scarlet")').click(); 788 | content = element('.doc-example-live [ng-view]').text(); 789 | expect(content).toMatch(/controller\: BookCntl/); 790 | expect(content).toMatch(/Book Id\: Scarlet/); 791 | }); 792 | 793 |
794 | */ 795 | 796 | 797 | /** 798 | * @ngdoc event 799 | * @name ngRoute.directive:ngView#$viewContentLoaded 800 | * @eventOf ngRoute.directive:ngView 801 | * @eventType emit on the current ngView scope 802 | * @description 803 | * Emitted every time the ngView content is reloaded. 804 | */ 805 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; 806 | function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { 807 | return { 808 | restrict: 'ECA', 809 | terminal: true, 810 | priority: 400, 811 | transclude: 'element', 812 | compile: function(element, attr, linker) { 813 | return function(scope, $element, attr) { 814 | var currentScope, 815 | currentElement, 816 | autoScrollExp = attr.autoscroll, 817 | onloadExp = attr.onload || ''; 818 | 819 | scope.$on('$routeChangeSuccess', update); 820 | update(); 821 | 822 | function cleanupLastView() { 823 | if (currentScope) { 824 | currentScope.$destroy(); 825 | currentScope = null; 826 | } 827 | if(currentElement) { 828 | $animate.leave(currentElement); 829 | currentElement = null; 830 | } 831 | } 832 | 833 | function update() { 834 | var locals = $route.current && $route.current.locals, 835 | template = locals && locals.$template; 836 | 837 | if (template) { 838 | var newScope = scope.$new(); 839 | linker(newScope, function(clone) { 840 | clone.html(template); 841 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 842 | if (angular.isDefined(autoScrollExp) 843 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 844 | $anchorScroll(); 845 | } 846 | }); 847 | 848 | cleanupLastView(); 849 | 850 | var link = $compile(clone.contents()), 851 | current = $route.current; 852 | 853 | currentScope = current.scope = newScope; 854 | currentElement = clone; 855 | 856 | if (current.controller) { 857 | locals.$scope = currentScope; 858 | var controller = $controller(current.controller, locals); 859 | if (current.controllerAs) { 860 | currentScope[current.controllerAs] = controller; 861 | } 862 | clone.data('$ngControllerController', controller); 863 | clone.children().data('$ngControllerController', controller); 864 | } 865 | 866 | link(currentScope); 867 | currentScope.$emit('$viewContentLoaded'); 868 | currentScope.$eval(onloadExp); 869 | }); 870 | } else { 871 | cleanupLastView(); 872 | } 873 | } 874 | }; 875 | } 876 | }; 877 | } 878 | 879 | 880 | })(window, window.angular); 881 | --------------------------------------------------------------------------------