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