angular-route.js 36 KB

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