angular-route.js 37 KB

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