angular-route.js 53 KB

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