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