You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1021 lines
38 KiB

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