diff options
| author | Igor Minar | 2013-06-05 15:30:31 -0700 | 
|---|---|---|
| committer | Igor Minar | 2013-06-06 17:07:12 -0700 | 
| commit | 5599b55b04788c2e327d7551a4a699d75516dd21 (patch) | |
| tree | dc080ce9639f44056eb6c476fb030923249ce265 /src/ngRoute | |
| parent | 7a5cfb593f27c28cee545974736632bf8da62fe8 (diff) | |
| download | angular.js-5599b55b04788c2e327d7551a4a699d75516dd21.tar.bz2 | |
refactor($route): pull $route and friends into angular-route.js
$route, $routeParams and ngView have been pulled from core angular.js
to angular-route.js/ngRoute module.
This is was done to in order keep the core focused on most commonly
used functionality and allow community routers to be freely used
instead of $route service.
There is no need to panic, angular-route will keep on being supported
by the angular team.
Note: I'm intentionally not fixing tutorial links. Tutorial will need
bigger changes and those should be done when we update tutorial to
1.2.
BREAKING CHANGE: applications that use $route will now need to load
angular-route.js file and define dependency on ngRoute module.
Before:
```
...
<script src="angular.js"></script>
...
var myApp = angular.module('myApp', ['someOtherModule']);
...
```
After:
```
...
<script src="angular.js"></script>
<script src="angular-route.js"></script>
...
var myApp = angular.module('myApp', ['ngRoute', 'someOtherModule']);
...
```
Closes #2804
Diffstat (limited to 'src/ngRoute')
| -rw-r--r-- | src/ngRoute/directive/ngView.js | 226 | ||||
| -rw-r--r-- | src/ngRoute/route.js | 519 | ||||
| -rw-r--r-- | src/ngRoute/routeParams.js | 33 | ||||
| -rw-r--r-- | src/ngRoute/routeUtils.js | 17 | 
4 files changed, 795 insertions, 0 deletions
| diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js new file mode 100644 index 00000000..935ba05d --- /dev/null +++ b/src/ngRoute/directive/ngView.js @@ -0,0 +1,226 @@ +'use strict'; + +ngRouteModule.directive('ngView', ngViewFactory); + +/** + * @ngdoc directive + * @name ngRoute.directive:ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter** + * and **leave** effects. + * + * @animations + * enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM) + * leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM + * + * @scope + * @example +    <example module="ngViewExample" deps="angular-route.js" animations="true"> +      <file name="index.html"> +        <div ng-controller="MainCntl as main"> +          Choose: +          <a href="Book/Moby">Moby</a> | +          <a href="Book/Moby/ch/1">Moby: Ch1</a> | +          <a href="Book/Gatsby">Gatsby</a> | +          <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | +          <a href="Book/Scarlet">Scarlet Letter</a><br/> + +          <div +            ng-view +            class="example-animate-container" +            ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div> +          <hr /> + +          <pre>$location.path() = {{main.$location.path()}}</pre> +          <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> +          <pre>$route.current.params = {{main.$route.current.params}}</pre> +          <pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre> +          <pre>$routeParams = {{main.$routeParams}}</pre> +        </div> +      </file> + +      <file name="book.html"> +        <div> +          controller: {{book.name}}<br /> +          Book Id: {{book.params.bookId}}<br /> +        </div> +      </file> + +      <file name="chapter.html"> +        <div> +          controller: {{chapter.name}}<br /> +          Book Id: {{chapter.params.bookId}}<br /> +          Chapter Id: {{chapter.params.chapterId}} +        </div> +      </file> + +      <file name="animations.css"> +        .example-leave, .example-enter { +          -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; +          -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; +          -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; +          -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; +          transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; +        } + +        .example-animate-container { +          position:relative; +          height:100px; +        } + +        .example-animate-container > * { +          display:block; +          width:100%; +          border-left:1px solid black; + +          position:absolute; +          top:0; +          left:0; +          right:0; +          bottom:0; +          padding:10px; +        } + +        .example-enter { +          left:100%; +        } +        .example-enter.example-enter-active { +          left:0; +        } + +        .example-leave { } +        .example-leave.example-leave-active { +          left:-100%; +        } +      </file> + +      <file name="script.js"> +        angular.module('ngViewExample', ['ngRoute'], function($routeProvider, $locationProvider) { +          $routeProvider.when('/Book/:bookId', { +            templateUrl: 'book.html', +            controller: BookCntl, +            controllerAs: 'book' +          }); +          $routeProvider.when('/Book/:bookId/ch/:chapterId', { +            templateUrl: 'chapter.html', +            controller: ChapterCntl, +            controllerAs: 'chapter' +          }); + +          // configure html5 to get links working on jsfiddle +          $locationProvider.html5Mode(true); +        }); + +        function MainCntl($route, $routeParams, $location) { +          this.$route = $route; +          this.$location = $location; +          this.$routeParams = $routeParams; +        } + +        function BookCntl($routeParams) { +          this.name = "BookCntl"; +          this.params = $routeParams; +        } + +        function ChapterCntl($routeParams) { +          this.name = "ChapterCntl"; +          this.params = $routeParams; +        } +      </file> + +      <file name="scenario.js"> +        it('should load and compile correct template', function() { +          element('a:contains("Moby: Ch1")').click(); +          var content = element('.doc-example-live [ng-view]').text(); +          expect(content).toMatch(/controller\: ChapterCntl/); +          expect(content).toMatch(/Book Id\: Moby/); +          expect(content).toMatch(/Chapter Id\: 1/); + +          element('a:contains("Scarlet")').click(); +          content = element('.doc-example-live [ng-view]').text(); +          expect(content).toMatch(/controller\: BookCntl/); +          expect(content).toMatch(/Book Id\: Scarlet/); +        }); +      </file> +    </example> + */ + + +/** + * @ngdoc event + * @name ngRoute.directive:ngView#$viewContentLoaded + * @eventOf ngRoute.directive:ngView + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animator']; +function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,   $animator) { +  return { +    restrict: 'ECA', +    terminal: true, +    link: function(scope, element, attr) { +      var lastScope, +          onloadExp = attr.onload || '', +          animate = $animator(scope, attr); + +      scope.$on('$routeChangeSuccess', update); +      update(); + + +      function destroyLastScope() { +        if (lastScope) { +          lastScope.$destroy(); +          lastScope = null; +        } +      } + +      function clearContent() { +        animate.leave(element.contents(), element); +        destroyLastScope(); +      } + +      function update() { +        var locals = $route.current && $route.current.locals, +            template = locals && locals.$template; + +        if (template) { +          clearContent(); +          var enterElements = jqLite('<div></div>').html(template).contents(); +          animate.enter(enterElements, element); + +          var link = $compile(enterElements), +              current = $route.current, +              controller; + +          lastScope = current.scope = scope.$new(); +          if (current.controller) { +            locals.$scope = lastScope; +            controller = $controller(current.controller, locals); +            if (current.controllerAs) { +              lastScope[current.controllerAs] = controller; +            } +            element.children().data('$ngControllerController', controller); +          } + +          link(lastScope); +          lastScope.$emit('$viewContentLoaded'); +          lastScope.$eval(onloadExp); + +          // $anchorScroll might listen on event... +          $anchorScroll(); +        } else { +          clearContent(); +        } +      } +    } +  }; +} diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js new file mode 100644 index 00000000..32b7c626 --- /dev/null +++ b/src/ngRoute/route.js @@ -0,0 +1,519 @@ +'use strict'; + +/** + * @ngdoc overview + * @name ngRoute + * @description + * + * Module that provides routing and deeplinking services and directives for angular apps. + */ + +var ngRouteModule = angular.module('ngRoute', ['ng']). +                        provider('$route', $RouteProvider); + +/** + * @ngdoc object + * @name ngRoute.$routeProvider + * @function + * + * @description + * + * Used for configuring routes. See {@link ngRoute.$route $route} for an example. + */ +function $RouteProvider(){ +  var routes = {}; + +  /** +   * @ngdoc method +   * @name ngRoute.$routeProvider#when +   * @methodOf ngRoute.$routeProvider +   * +   * @param {string} path Route path (matched against `$location.path`). If `$location.path` +   *    contains redundant trailing slash or is missing one, the route will still match and the +   *    `$location.path` will be updated to add or drop the trailing slash to exactly match the +   *    route definition. +   * +   *      * `path` can contain named groups starting with a colon (`:name`). All characters up +   *        to the next slash are matched and stored in `$routeParams` under the given `name` +   *        when the route matches. +   *      * `path` can contain named groups starting with a star (`*name`). All characters are +   *        eagerly stored in `$routeParams` under the given `name` when the route matches. +   * +   *    For example, routes like `/color/:color/largecode/*largecode/edit` will match +   *    `/color/brown/largecode/code/with/slashs/edit` and extract: +   * +   *      * `color: brown` +   *      * `largecode: code/with/slashs`. +   * +   * +   * @param {Object} route Mapping information to be assigned to `$route.current` on route +   *    match. +   * +   *    Object properties: +   * +   *    - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly +   *      created scope or the name of a {@link angular.Module#controller registered controller} +   *      if passed as a string. +   *    - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be +   *      published to scope under the `controllerAs` name. +   *    - `template` – `{string=|function()=}` – html template as a string or function that returns +   *      an html template as a string which should be used by {@link ngRoute.directive:ngView ngView} or +   *      {@link ng.directive:ngInclude ngInclude} directives. +   *      This property takes precedence over `templateUrl`. +   * +   *      If `template` is a function, it will be called with the following parameters: +   * +   *      - `{Array.<Object>}` - route parameters extracted from the current +   *        `$location.path()` by applying the current route +   * +   *    - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html +   *      template that should be used by {@link ngRoute.directive:ngView ngView}. +   * +   *      If `templateUrl` is a function, it will be called with the following parameters: +   * +   *      - `{Array.<Object>}` - route parameters extracted from the current +   *        `$location.path()` by applying the current route +   * +   *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should +   *      be injected into the controller. If any of these dependencies are promises, they will be +   *      resolved and converted to a value before the controller is instantiated and the +   *      `$routeChangeSuccess` event is fired. The map object is: +   * +   *      - `key` – `{string}`: a name of a dependency to be injected into the controller. +   *      - `factory` - `{string|function}`: If `string` then it is an alias for a service. +   *        Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} +   *        and the return value is treated as the dependency. If the result is a promise, it is resolved +   *        before its value is injected into the controller. +   * +   *    - `redirectTo` – {(string|function())=} – value to update +   *      {@link ng.$location $location} path with and trigger route redirection. +   * +   *      If `redirectTo` is a function, it will be called with the following parameters: +   * +   *      - `{Object.<string>}` - route parameters extracted from the current +   *        `$location.path()` by applying the current route templateUrl. +   *      - `{string}` - current `$location.path()` +   *      - `{Object}` - current `$location.search()` +   * +   *      The custom `redirectTo` function is expected to return a string which will be used +   *      to update `$location.path()` and `$location.search()`. +   * +   *    - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() +   *    changes. +   * +   *      If the option is set to `false` and url in the browser changes, then +   *      `$routeUpdate` event is broadcasted on the root scope. +   * +   *    - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive +   * +   *      If the option is set to `true`, then the particular route can be matched without being +   *      case sensitive +   * +   * @returns {Object} self +   * +   * @description +   * Adds a new route definition to the `$route` service. +   */ +  this.when = function(path, route) { +    routes[path] = extend({reloadOnSearch: true, caseInsensitiveMatch: false}, route); + +    // create redirection for trailing slashes +    if (path) { +      var redirectPath = (path[path.length-1] == '/') +          ? path.substr(0, path.length-1) +          : path +'/'; + +      routes[redirectPath] = {redirectTo: path}; +    } + +    return this; +  }; + +  /** +   * @ngdoc method +   * @name ngRoute.$routeProvider#otherwise +   * @methodOf ngRoute.$routeProvider +   * +   * @description +   * Sets route definition that will be used on route change when no other route definition +   * is matched. +   * +   * @param {Object} params Mapping information to be assigned to `$route.current`. +   * @returns {Object} self +   */ +  this.otherwise = function(params) { +    this.when(null, params); +    return this; +  }; + + +  this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', +      function( $rootScope,   $location,   $routeParams,   $q,   $injector,   $http,   $templateCache) { + +    /** +     * @ngdoc object +     * @name ngRoute.$route +     * @requires $location +     * @requires $routeParams +     * +     * @property {Object} current Reference to the current route definition. +     * The route definition contains: +     * +     *   - `controller`: The controller constructor as define in route definition. +     *   - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for +     *     controller instantiation. The `locals` contain +     *     the resolved values of the `resolve` map. Additionally the `locals` also contain: +     * +     *     - `$scope` - The current route scope. +     *     - `$template` - The current route template HTML. +     * +     * @property {Array.<Object>} routes Array of all configured routes. +     * +     * @description +     * Is used for deep-linking URLs to controllers and views (HTML partials). +     * It watches `$location.url()` and tries to map the path to an existing route definition. +     * +     * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. +     * +     * The `$route` service is typically used in conjunction with {@link ngRoute.directive:ngView ngView} +     * directive and the {@link ngRoute.$routeParams $routeParams} service. +     * +     * @example +       This example shows how changing the URL hash causes the `$route` to match a route against the +       URL, and the `ngView` pulls in the partial. + +       Note that this example is using {@link ng.directive:script inlined templates} +       to get it working on jsfiddle as well. + +     <example module="ngView" deps="angular-route.js"> +       <file name="index.html"> +         <div ng-controller="MainCntl"> +           Choose: +           <a href="Book/Moby">Moby</a> | +           <a href="Book/Moby/ch/1">Moby: Ch1</a> | +           <a href="Book/Gatsby">Gatsby</a> | +           <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | +           <a href="Book/Scarlet">Scarlet Letter</a><br/> + +           <div ng-view></div> +           <hr /> + +           <pre>$location.path() = {{$location.path()}}</pre> +           <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> +           <pre>$route.current.params = {{$route.current.params}}</pre> +           <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> +           <pre>$routeParams = {{$routeParams}}</pre> +         </div> +       </file> + +       <file name="book.html"> +         controller: {{name}}<br /> +         Book Id: {{params.bookId}}<br /> +       </file> + +       <file name="chapter.html"> +         controller: {{name}}<br /> +         Book Id: {{params.bookId}}<br /> +         Chapter Id: {{params.chapterId}} +       </file> + +       <file name="script.js"> +         angular.module('ngView', ['ngRoute'], function($routeProvider, $locationProvider) { +           $routeProvider.when('/Book/:bookId', { +             templateUrl: 'book.html', +             controller: BookCntl, +             resolve: { +               // I will cause a 1 second delay +               delay: function($q, $timeout) { +                 var delay = $q.defer(); +                 $timeout(delay.resolve, 1000); +                 return delay.promise; +               } +             } +           }); +           $routeProvider.when('/Book/:bookId/ch/:chapterId', { +             templateUrl: 'chapter.html', +             controller: ChapterCntl +           }); + +           // configure html5 to get links working on jsfiddle +           $locationProvider.html5Mode(true); +         }); + +         function MainCntl($scope, $route, $routeParams, $location) { +           $scope.$route = $route; +           $scope.$location = $location; +           $scope.$routeParams = $routeParams; +         } + +         function BookCntl($scope, $routeParams) { +           $scope.name = "BookCntl"; +           $scope.params = $routeParams; +         } + +         function ChapterCntl($scope, $routeParams) { +           $scope.name = "ChapterCntl"; +           $scope.params = $routeParams; +         } +       </file> + +       <file name="scenario.js"> +         it('should load and compile correct template', function() { +           element('a:contains("Moby: Ch1")').click(); +           var content = element('.doc-example-live [ng-view]').text(); +           expect(content).toMatch(/controller\: ChapterCntl/); +           expect(content).toMatch(/Book Id\: Moby/); +           expect(content).toMatch(/Chapter Id\: 1/); + +           element('a:contains("Scarlet")').click(); +           sleep(2); // promises are not part of scenario waiting +           content = element('.doc-example-live [ng-view]').text(); +           expect(content).toMatch(/controller\: BookCntl/); +           expect(content).toMatch(/Book Id\: Scarlet/); +         }); +       </file> +     </example> +     */ + +    /** +     * @ngdoc event +     * @name ngRoute.$route#$routeChangeStart +     * @eventOf ngRoute.$route +     * @eventType broadcast on root scope +     * @description +     * Broadcasted before a route change. At this  point the route services starts +     * resolving all of the dependencies needed for the route change to occurs. +     * Typically this involves fetching the view template as well as any dependencies +     * defined in `resolve` route property. Once  all of the dependencies are resolved +     * `$routeChangeSuccess` is fired. +     * +     * @param {Route} next Future route information. +     * @param {Route} current Current route information. +     */ + +    /** +     * @ngdoc event +     * @name ngRoute.$route#$routeChangeSuccess +     * @eventOf ngRoute.$route +     * @eventType broadcast on root scope +     * @description +     * Broadcasted after a route dependencies are resolved. +     * {@link ngRoute.directive:ngView ngView} listens for the directive +     * to instantiate the controller and render the view. +     * +     * @param {Object} angularEvent Synthetic event object. +     * @param {Route} current Current route information. +     * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. +     */ + +    /** +     * @ngdoc event +     * @name ngRoute.$route#$routeChangeError +     * @eventOf ngRoute.$route +     * @eventType broadcast on root scope +     * @description +     * Broadcasted if any of the resolve promises are rejected. +     * +     * @param {Route} current Current route information. +     * @param {Route} previous Previous route information. +     * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. +     */ + +    /** +     * @ngdoc event +     * @name ngRoute.$route#$routeUpdate +     * @eventOf ngRoute.$route +     * @eventType broadcast on root scope +     * @description +     * +     * The `reloadOnSearch` property has been set to false, and we are reusing the same +     * instance of the Controller. +     */ + +    var forceReload = false, +        $route = { +          routes: routes, + +          /** +           * @ngdoc method +           * @name ngRoute.$route#reload +           * @methodOf ngRoute.$route +           * +           * @description +           * Causes `$route` service to reload the current route even if +           * {@link ng.$location $location} hasn't changed. +           * +           * As a result of that, {@link ngRoute.directive:ngView ngView} +           * creates new scope, reinstantiates the controller. +           */ +          reload: function() { +            forceReload = true; +            $rootScope.$evalAsync(updateRoute); +          } +        }; + +    $rootScope.$on('$locationChangeSuccess', updateRoute); + +    return $route; + +    ///////////////////////////////////////////////////// + +    /** +     * @param on {string} current url +     * @param when {string} route when template to match the url against +     * @param whenProperties {Object} properties to define when's matching behavior +     * @return {?Object} +     */ +    function switchRouteMatcher(on, when, whenProperties) { +      // TODO(i): this code is convoluted and inefficient, we should construct the route matching +      //   regex only once and then reuse it + +      // Escape regexp special characters. +      when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$'; + +      var regex = '', +          params = [], +          dst = {}; + +      var re = /\\([:*])(\w+)/g, +          paramMatch, +          lastMatchedIndex = 0; + +      while ((paramMatch = re.exec(when)) !== null) { +        // Find each :param in `when` and replace it with a capturing group. +        // Append all other sections of when unchanged. +        regex += when.slice(lastMatchedIndex, paramMatch.index); +        switch(paramMatch[1]) { +          case ':': +            regex += '([^\\/]*)'; +            break; +          case '*': +            regex += '(.*)'; +            break; +        } +        params.push(paramMatch[2]); +        lastMatchedIndex = re.lastIndex; +      } +      // Append trailing path part. +      regex += when.substr(lastMatchedIndex); + +      var match = on.match(new RegExp(regex, whenProperties.caseInsensitiveMatch ? 'i' : '')); +      if (match) { +        forEach(params, function(name, index) { +          dst[name] = match[index + 1]; +        }); +      } +      return match ? dst : null; +    } + +    function updateRoute() { +      var next = parseRoute(), +          last = $route.current; + +      if (next && last && next.$$route === last.$$route +          && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { +        last.params = next.params; +        copy(last.params, $routeParams); +        $rootScope.$broadcast('$routeUpdate', last); +      } else if (next || last) { +        forceReload = false; +        $rootScope.$broadcast('$routeChangeStart', next, last); +        $route.current = next; +        if (next) { +          if (next.redirectTo) { +            if (isString(next.redirectTo)) { +              $location.path(interpolate(next.redirectTo, next.params)).search(next.params) +                       .replace(); +            } else { +              $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) +                       .replace(); +            } +          } +        } + +        $q.when(next). +          then(function() { +            if (next) { +              var locals = extend({}, next.resolve), +                  template; + +              forEach(locals, function(value, key) { +                locals[key] = isString(value) ? $injector.get(value) : $injector.invoke(value); +              }); + +              if (isDefined(template = next.template)) { +                if (isFunction(template)) { +                  template = template(next.params); +                } +              } else if (isDefined(template = next.templateUrl)) { +                if (isFunction(template)) { +                  template = template(next.params); +                } +                if (isDefined(template)) { +                  next.loadedTemplateUrl = template; +                  template = $http.get(template, {cache: $templateCache}). +                      then(function(response) { return response.data; }); +                } +              } +              if (isDefined(template)) { +                locals['$template'] = template; +              } +              return $q.all(locals); +            } +          }). +          // after route change +          then(function(locals) { +            if (next == $route.current) { +              if (next) { +                next.locals = locals; +                copy(next.params, $routeParams); +              } +              $rootScope.$broadcast('$routeChangeSuccess', next, last); +            } +          }, function(error) { +            if (next == $route.current) { +              $rootScope.$broadcast('$routeChangeError', next, last, error); +            } +          }); +      } +    } + + +    /** +     * @returns the current active route, by matching it against the URL +     */ +    function parseRoute() { +      // Match a route +      var params, match; +      forEach(routes, function(route, path) { +        if (!match && (params = switchRouteMatcher($location.path(), path, route))) { +          match = inherit(route, { +            params: extend({}, $location.search(), params), +            pathParams: params}); +          match.$$route = route; +        } +      }); +      // No route matched; fallback to "otherwise" route +      return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); +    } + +    /** +     * @returns interpolation of the redirect path with the parameters +     */ +    function interpolate(string, params) { +      var result = []; +      forEach((string||'').split(':'), function(segment, i) { +        if (i == 0) { +          result.push(segment); +        } else { +          var segmentMatch = segment.match(/(\w+)(.*)/); +          var key = segmentMatch[1]; +          result.push(params[key]); +          result.push(segmentMatch[2] || ''); +          delete params[key]; +        } +      }); +      return result.join(''); +    } +  }]; +} diff --git a/src/ngRoute/routeParams.js b/src/ngRoute/routeParams.js new file mode 100644 index 00000000..0c86e89d --- /dev/null +++ b/src/ngRoute/routeParams.js @@ -0,0 +1,33 @@ +'use strict'; + +ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + +/** + * @ngdoc object + * @name ngRoute.$routeParams + * @requires $route + * + * @description + * Current set of route parameters. The route parameters are a combination of the + * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters + * are extracted when the {@link ngRoute.$route $route} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * @example + * <pre> + *  // Given: + *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby + *  // Route: /Chapter/:chapterId/Section/:sectionId + *  // + *  // Then + *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} + * </pre> + */ +function $RouteParamsProvider() { +  this.$get = function() { return {}; }; +} diff --git a/src/ngRoute/routeUtils.js b/src/ngRoute/routeUtils.js new file mode 100644 index 00000000..0cff7213 --- /dev/null +++ b/src/ngRoute/routeUtils.js @@ -0,0 +1,17 @@ +'use strict'; + +var copy = angular.copy, +    equals = angular.equals, +    extend = angular.extend, +    forEach = angular.forEach, +    isDefined = angular.isDefined, +    isFunction = angular.isFunction, +    isString = angular.isString, +    jqLite = angular.element, +    noop = angular.noop, +    toJson = angular.toJson; + + +function inherit(parent, extra) { +  return extend(new (extend(function() {}, {prototype:parent}))(), extra); +} | 
