From 8c9454ac4fb0a4317aac0f90f2c4dbd0b2aee7a0 Mon Sep 17 00:00:00 2001 From: Richard Collins Date: Fri, 26 Jul 2013 15:10:47 +1200 Subject: [PATCH 1/2] Change router so route can be a callback function --- src/ngRoute/route.js | 49 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 743897d625c5..8b1608f75c45 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -18,7 +18,11 @@ var ngRouteModule = angular.module('ngRoute', ['ng']). * * @description * - * Used for configuring routes. See {@link ngRoute.$route $route} for an example. + * Used for configuring routes. `when` and `otherwise` are used to build a routing table. When + * `$location.url()` (i.e. the address bar) is changed the routing table is consulted to determine + * the new contents of the ngView tag. + * + * See {@link ngRoute.$route $route} for an example. */ function $RouteProvider(){ var routes = {}; @@ -45,9 +49,15 @@ function $RouteProvider(){ * * `color: brown` * * `largecode: code/with/slashs`. * + * If this route matching scheme is not expressive enough, a callback function can be passed + * as the `route` parameter to apply additional routing logic. Alternatively a different + * module can be used to provide an alternative implementation of the `$route` service. * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. + * @param {Object|function()} route Mapping information to be assigned to `$route.current` which + * determines the contents of ngView on route match. + * + * Usually you will provide an object containing the mapping information. However you can also + * provide a callback function if you need more flexibility. * * Object properties: * @@ -111,13 +121,24 @@ function $RouteProvider(){ * If the option is set to `true`, then the particular route can be matched without being * case sensitive * + * Callback function: + * + * When a callback function is provided, it must return an object in the above format. The + * function will be called each time the route matches with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{Object}` - current `$location.search()` + * * @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); + routes[path] = isFunction(route) + ? route + : extend({reloadOnSearch: true, caseInsensitiveMatch: false}, route); // create redirection for trailing slashes if (path) { @@ -137,14 +158,14 @@ function $RouteProvider(){ * @methodOf ngRoute.$routeProvider * * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. + * Sets route definition that will be used when no other route matches. * - * @param {Object} params Mapping information to be assigned to `$route.current`. + * @param {Object} route Mapping information to be assigned to `$route.current`. See the `route` + * parameter of the `when` method for full details. * @returns {Object} self */ - this.otherwise = function(params) { - this.when(null, params); + this.otherwise = function(route) { + this.when(null, route); return this; }; @@ -490,8 +511,16 @@ function $RouteProvider(){ var params, match; forEach(routes, function(route, path) { if (!match && (params = switchRouteMatcher($location.path(), path, route))) { + var search = $location.search(); + + if (isFunction(route)) { + // route is a function, call it to get the actual route to use + // route function can update params or search + route = route(params, search); + } + match = inherit(route, { - params: extend({}, $location.search(), params), + params: extend({}, search, params), pathParams: params}); match.$$route = route; } From f08021bcca90269ecea331a2713204fd58d94502 Mon Sep 17 00:00:00 2001 From: Richard Collins Date: Fri, 26 Jul 2013 16:33:44 +1200 Subject: [PATCH 2/2] feat($routeProvider): Route can be a callback function The route parameter of $routeProvider.when() and $routeProvider.otherwise() can now be a callback function so that more elaborate routing logic can be used without replacing the standard router. --- test/ngRoute/routeSpec.js | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 13d149a12795..12f6f39c4429 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -185,6 +185,71 @@ describe('$route', function() { }); }); + it('should route and fire change event when route is a callback funtion', function() { + var log = '', + lastRoute, + nextRoute; + + function controller1() {} + function controller2() {} + function controller3() {} + + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + module(function($routeProvider) { + $routeProvider.when('/route1/:p1/:p2', + function (params, search) { + if (search.q == 'BAZ') + return { controller: controller3, templateUrl: "baz.html" }; + else if (isNumeric(params.p1)) + return { controller: controller1, templateUrl: "foo.html" }; + else + return { controller: controller2, templateUrl: "bar.html" }; + }); + }); + + inject(function($route, $location, $rootScope) { + $rootScope.$on('$routeChangeStart', function(event, next, current) { + log += 'before();'; + expect(current).toBe($route.current); + lastRoute = current; + nextRoute = next; + }); + $rootScope.$on('$routeChangeSuccess', function(event, current, last) { + log += 'after();'; + expect(current).toBe($route.current); + expect(lastRoute).toBe(last); + expect(nextRoute).toBe(current); + }); + + $location.path('/route1/123/b'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(log).toEqual('before();after();'); + expect($route.current.controller).toEqual(controller1); + expect($route.current.locals.$template).toEqual('foo'); + + log = ''; + $location.path('/route1/abc/b'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(log).toEqual('before();after();'); + expect($route.current.controller).toEqual(controller2); + expect($route.current.locals.$template).toEqual('bar'); + + log = ''; + $location.path('/route1/abc/b').search('q=BAZ'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(log).toEqual('before();after();'); + expect($route.current.controller).toEqual(controller3); + expect($route.current.locals.$template).toEqual('baz'); + }); + }); + + it('should not change route when location is canceled', function() { module(function($routeProvider) {