Skip to content

Instantly share code, notes, and snippets.

@vladimir-ivanov
Created July 17, 2013 15:47
Show Gist options
  • Save vladimir-ivanov/6021821 to your computer and use it in GitHub Desktop.
Save vladimir-ivanov/6021821 to your computer and use it in GitHub Desktop.
Adding support for Regex in $routeProvider for angularjs. Makes the matches available in $routeParams Need to add App.provider('$route', App.Lib.RouteProviderRegex); And the actual $routeProvider.when(/someRegexHere/);
/**
* @ngdoc object
* @name ng.$routeProvider
* @function
*
* @example
* add this to the App
* App.provider('$route', App.Lib.RouteProviderRegex);
*
* And
$routeProvider
.when('/pages/:url', {
id: 'pages',
templateUrl: '/javascripts/partials/page.html',
controller: App.Controller.Page
})
// has to be a regex object
.when(/^\/pages\/(.*$)/, {
id: 'pages',
templateUrl: '/javascripts/partials/page.html',
controller: App.Controller.Page
})
Will make the matches available in $routeParams
* Used for configuring routes. See {@link ng.$route $route} for an example.
*/
App.Lib.RouteProviderRegex = function () {
'use strict';
var routes = {};
function extend(dst) {
var h = dst.$$hashKey;
angular.forEach(arguments, function(obj){
if (obj !== dst) {
angular.forEach(obj, function(value, key){
dst[key] = value;
});
}
});
setHashKey(dst,h);
return dst;
}
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
}
else {
delete obj.$$hashKey;
}
}
function int(str) {
return parseInt(str, 10);
}
function inherit(parent, extra) {
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
}
function isWindow(obj) {
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
}
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
function isFile(obj) {
return toString.apply(obj) === '[object File]';
}
function isBoolean(value) {
return typeof value == 'boolean';
}
function trim(value) {
return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
}
function isArray(value) {
return toString.apply(value) == '[object Array]';
}
function isArrayLike(obj) {
if (!obj || (typeof obj.length !== 'number')) return false;
// We have on object which has length property. Should we treat it as array?
if (typeof obj.hasOwnProperty != 'function' &&
typeof obj.constructor != 'function') {
// This is here for IE8: it is a bogus object treat it as array;
return true;
} else {
return obj instanceof JQLite || // JQLite
(jQuery && obj instanceof jQuery) || // jQuery
toString.call(obj) !== '[object Object]' || // some browser native object
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
}
}
/**
* @ngdoc method
* @name ng.$routeProvider#when
* @methodOf ng.$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.
*
* @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.
* - `template` – `{string=}` – html template as a string that should be used by
* {@link ng.directive:ngView ngView} or
* {@link ng.directive:ngInclude ngInclude} directives.
* this property takes precedence over `templateUrl`.
* - `templateUrl` – `{string=}` – path to an html template that should be used by
* {@link ng.directive:ngView ngView}.
* - `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.
*
* @returns {Object} self
*
* @description
* Adds a new route definition to the `$route` service.
*/
this.when = function (path, route) {
routes[path] = extend({reloadOnSearch: true, path: path}, route);
// create redirection for trailing slashes
if (path && !(path instanceof RegExp)) {
var redirectPath = (path[path.length - 1] == '/')
? path.substr(0, path.length - 1)
: path + '/';
routes[redirectPath] = {redirectTo: path};
}
return this;
};
/**
* @ngdoc method
* @name ng.$routeProvider#otherwise
* @methodOf ng.$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 ng.$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 ng.$routeProvider $routeProvider}'s API.
*
* The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
* directive and the {@link ng.$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">
<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', [], 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 ng.$route#$routeChangeStart
* @eventOf ng.$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 ng.$route#$routeChangeSuccess
* @eventOf ng.$route
* @eventType broadcast on root scope
* @description
* Broadcasted after a route dependencies are resolved.
* {@link ng.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 ng.$route#$routeChangeError
* @eventOf ng.$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 ng.$route#$routeUpdate
* @eventOf ng.$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 ng.$route#reload
* @methodOf ng.$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 ng.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
* @return {?Object}
*/
function switchRouteMatcher(on, when) {
// TODO(i): this code is convoluted and inefficient, we should construct the route matching
// regex only once and then reuse it
if (!(when instanceof RegExp)) {
// 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);
regex += '([^\\/]*)';
params.push(paramMatch[1]);
lastMatchedIndex = re.lastIndex;
}
// Append trailing path part.
regex += when.substr(lastMatchedIndex);
var match = on.match(new RegExp(regex));
if (match) {
angular.forEach(params, function (name, index) {
dst[name] = match[index + 1];
});
}
} else {
var match = on.match(when);
dst = match.length > 0 ? match : null;
}
return match ? dst : null;
}
function updateRoute() {
var next = parseRoute(),
last = $route.current;
if (next && last && next.$$route === last.$$route
&& angular.equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
last.params = next.params;
angular.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 keys = [],
values = [],
template;
angular.forEach(next.resolve || {}, function (value, key) {
keys.push(key);
values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
});
if (angular.isDefined(template = next.template)) {
} else if (angular.isDefined(template = next.templateUrl)) {
template = $http.get(template, {cache: $templateCache}).
then(function (response) {
return response.data;
});
}
if (angular.isDefined(template)) {
keys.push('$template');
values.push(template);
}
return $q.all(values).then(function (values) {
var locals = {};
angular.forEach(values, function (value, index) {
locals[keys[index]] = value;
});
return locals;
});
}
}).
// after route change
then(function (locals) {
if (next == $route.current) {
if (next) {
next.locals = locals;
angular.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;
angular.forEach(routes, function (route, path) {
if (!match && (params = switchRouteMatcher($location.path(), route.path || path))) {
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 parametrs
*/
function interpolate(string, params) {
var result = [];
angular.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('');
}
}];
};
@vladimir-ivanov
Copy link
Author

Hi guys, apologies for the late reply, this code is meant to work with an old version of angular - as per 2 years ago (believe it was 1.0 at the time).
Which version of angular are you targeting please?
Can have a look and update if necessary.
Thanks

@vladimir-ivanov
Copy link
Author

As for usage -

add to your app like this:

myApp.provider('$route', App.Lib.RouteProviderRegex); - you can change the name [App.Lib.RouteProviderRegex] to whatever you find fit e.g. $routeProviderRegex etc

then where you are defining your routes - $routeProvider.when(/categoryId/[0-9]*/) etc

@highfeed
Copy link

highfeed commented Dec 8, 2015

AngularJS v1.4.3

@highfeed
Copy link

Вова, не работает. Что делать?)

@highfeed
Copy link

До сих пор не работает)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment