暂无描述

angular-route.js 38KB

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