angular-route.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. /**
  2. * @license AngularJS v1.5.5
  3. * (c) 2010-2016 Google, Inc. http://angularjs.org
  4. * License: MIT
  5. */
  6. (function(window, angular) {'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. * @knownIssue If `ngView` is contained in an asynchronously loaded template (e.g. in another
  710. * directive's templateUrl or in a template loaded using `ngInclude`), then you need to
  711. * make sure that `$route` is instantiated in time to capture the initial
  712. * `$locationChangeStart` event and load the appropriate view. One way to achieve this
  713. * is to have it as a dependency in a `.run` block:
  714. * `myModule.run(['$route', function() {}]);`
  715. *
  716. * @scope
  717. * @priority 400
  718. * @param {string=} onload Expression to evaluate whenever the view updates.
  719. *
  720. * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
  721. * $anchorScroll} to scroll the viewport after the view is updated.
  722. *
  723. * - If the attribute is not set, disable scrolling.
  724. * - If the attribute is set without value, enable scrolling.
  725. * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
  726. * as an expression yields a truthy value.
  727. * @example
  728. <example name="ngView-directive" module="ngViewExample"
  729. deps="angular-route.js;angular-animate.js"
  730. animations="true" fixBase="true">
  731. <file name="index.html">
  732. <div ng-controller="MainCtrl as main">
  733. Choose:
  734. <a href="Book/Moby">Moby</a> |
  735. <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  736. <a href="Book/Gatsby">Gatsby</a> |
  737. <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  738. <a href="Book/Scarlet">Scarlet Letter</a><br/>
  739. <div class="view-animate-container">
  740. <div ng-view class="view-animate"></div>
  741. </div>
  742. <hr />
  743. <pre>$location.path() = {{main.$location.path()}}</pre>
  744. <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
  745. <pre>$route.current.params = {{main.$route.current.params}}</pre>
  746. <pre>$routeParams = {{main.$routeParams}}</pre>
  747. </div>
  748. </file>
  749. <file name="book.html">
  750. <div>
  751. controller: {{book.name}}<br />
  752. Book Id: {{book.params.bookId}}<br />
  753. </div>
  754. </file>
  755. <file name="chapter.html">
  756. <div>
  757. controller: {{chapter.name}}<br />
  758. Book Id: {{chapter.params.bookId}}<br />
  759. Chapter Id: {{chapter.params.chapterId}}
  760. </div>
  761. </file>
  762. <file name="animations.css">
  763. .view-animate-container {
  764. position:relative;
  765. height:100px!important;
  766. background:white;
  767. border:1px solid black;
  768. height:40px;
  769. overflow:hidden;
  770. }
  771. .view-animate {
  772. padding:10px;
  773. }
  774. .view-animate.ng-enter, .view-animate.ng-leave {
  775. transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
  776. display:block;
  777. width:100%;
  778. border-left:1px solid black;
  779. position:absolute;
  780. top:0;
  781. left:0;
  782. right:0;
  783. bottom:0;
  784. padding:10px;
  785. }
  786. .view-animate.ng-enter {
  787. left:100%;
  788. }
  789. .view-animate.ng-enter.ng-enter-active {
  790. left:0;
  791. }
  792. .view-animate.ng-leave.ng-leave-active {
  793. left:-100%;
  794. }
  795. </file>
  796. <file name="script.js">
  797. angular.module('ngViewExample', ['ngRoute', 'ngAnimate'])
  798. .config(['$routeProvider', '$locationProvider',
  799. function($routeProvider, $locationProvider) {
  800. $routeProvider
  801. .when('/Book/:bookId', {
  802. templateUrl: 'book.html',
  803. controller: 'BookCtrl',
  804. controllerAs: 'book'
  805. })
  806. .when('/Book/:bookId/ch/:chapterId', {
  807. templateUrl: 'chapter.html',
  808. controller: 'ChapterCtrl',
  809. controllerAs: 'chapter'
  810. });
  811. $locationProvider.html5Mode(true);
  812. }])
  813. .controller('MainCtrl', ['$route', '$routeParams', '$location',
  814. function($route, $routeParams, $location) {
  815. this.$route = $route;
  816. this.$location = $location;
  817. this.$routeParams = $routeParams;
  818. }])
  819. .controller('BookCtrl', ['$routeParams', function($routeParams) {
  820. this.name = "BookCtrl";
  821. this.params = $routeParams;
  822. }])
  823. .controller('ChapterCtrl', ['$routeParams', function($routeParams) {
  824. this.name = "ChapterCtrl";
  825. this.params = $routeParams;
  826. }]);
  827. </file>
  828. <file name="protractor.js" type="protractor">
  829. it('should load and compile correct template', function() {
  830. element(by.linkText('Moby: Ch1')).click();
  831. var content = element(by.css('[ng-view]')).getText();
  832. expect(content).toMatch(/controller\: ChapterCtrl/);
  833. expect(content).toMatch(/Book Id\: Moby/);
  834. expect(content).toMatch(/Chapter Id\: 1/);
  835. element(by.partialLinkText('Scarlet')).click();
  836. content = element(by.css('[ng-view]')).getText();
  837. expect(content).toMatch(/controller\: BookCtrl/);
  838. expect(content).toMatch(/Book Id\: Scarlet/);
  839. });
  840. </file>
  841. </example>
  842. */
  843. /**
  844. * @ngdoc event
  845. * @name ngView#$viewContentLoaded
  846. * @eventType emit on the current ngView scope
  847. * @description
  848. * Emitted every time the ngView content is reloaded.
  849. */
  850. ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
  851. function ngViewFactory($route, $anchorScroll, $animate) {
  852. return {
  853. restrict: 'ECA',
  854. terminal: true,
  855. priority: 400,
  856. transclude: 'element',
  857. link: function(scope, $element, attr, ctrl, $transclude) {
  858. var currentScope,
  859. currentElement,
  860. previousLeaveAnimation,
  861. autoScrollExp = attr.autoscroll,
  862. onloadExp = attr.onload || '';
  863. scope.$on('$routeChangeSuccess', update);
  864. update();
  865. function cleanupLastView() {
  866. if (previousLeaveAnimation) {
  867. $animate.cancel(previousLeaveAnimation);
  868. previousLeaveAnimation = null;
  869. }
  870. if (currentScope) {
  871. currentScope.$destroy();
  872. currentScope = null;
  873. }
  874. if (currentElement) {
  875. previousLeaveAnimation = $animate.leave(currentElement);
  876. previousLeaveAnimation.then(function() {
  877. previousLeaveAnimation = null;
  878. });
  879. currentElement = null;
  880. }
  881. }
  882. function update() {
  883. var locals = $route.current && $route.current.locals,
  884. template = locals && locals.$template;
  885. if (angular.isDefined(template)) {
  886. var newScope = scope.$new();
  887. var current = $route.current;
  888. // Note: This will also link all children of ng-view that were contained in the original
  889. // html. If that content contains controllers, ... they could pollute/change the scope.
  890. // However, using ng-view on an element with additional content does not make sense...
  891. // Note: We can't remove them in the cloneAttchFn of $transclude as that
  892. // function is called before linking the content, which would apply child
  893. // directives to non existing elements.
  894. var clone = $transclude(newScope, function(clone) {
  895. $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() {
  896. if (angular.isDefined(autoScrollExp)
  897. && (!autoScrollExp || scope.$eval(autoScrollExp))) {
  898. $anchorScroll();
  899. }
  900. });
  901. cleanupLastView();
  902. });
  903. currentElement = clone;
  904. currentScope = current.scope = newScope;
  905. currentScope.$emit('$viewContentLoaded');
  906. currentScope.$eval(onloadExp);
  907. } else {
  908. cleanupLastView();
  909. }
  910. }
  911. }
  912. };
  913. }
  914. // This directive is called during the $transclude call of the first `ngView` directive.
  915. // It will replace and compile the content of the element with the loaded template.
  916. // We need this directive so that the element content is already filled when
  917. // the link function of another directive on the same element as ngView
  918. // is called.
  919. ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
  920. function ngViewFillContentFactory($compile, $controller, $route) {
  921. return {
  922. restrict: 'ECA',
  923. priority: -400,
  924. link: function(scope, $element) {
  925. var current = $route.current,
  926. locals = current.locals;
  927. $element.html(locals.$template);
  928. var link = $compile($element.contents());
  929. if (current.controller) {
  930. locals.$scope = scope;
  931. var controller = $controller(current.controller, locals);
  932. if (current.controllerAs) {
  933. scope[current.controllerAs] = controller;
  934. }
  935. $element.data('$ngControllerController', controller);
  936. $element.children().data('$ngControllerController', controller);
  937. }
  938. scope[current.resolveAs || '$resolve'] = locals;
  939. link(scope);
  940. }
  941. };
  942. }
  943. })(window, window.angular);