Skip to content

Route provider parameters and converters

Jens Melgaard edited this page Sep 25, 2013 · 11 revisions

Parameter support has been extended so they now support catch all and converters. Converters can provide two things.

  1. Parameters can be validated already, which means that different parameters values can actually target different routes.
  2. Parameters can be converted before they are passed on from the route provider.

In order to use converters, a new syntax for parameters has to be used, this means that we use curly brackets ({}) for parameters that uses anything else than the standard converter, for regular parameters the colon syntax still works (:).

Catch all Parameters

Catch all parameters can be defined for both the old and the new syntax by adding a * in front of the name, e.g. :*paramName, {*paramName}, {converter:*paramName} or {converter(withParam):*paramName}. When a catch all is defined it will swallow the rest of the URL, and so it should only be used on the last parameter in a route.

It is possible to defined more specific routes before a more general catch all, e.g:

angular.module('phonecat', ['dotjem.routing']).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider
      .when('/phones/{num:phoneId}', { state: 'phones.detail' })
      .when('/phones/:*url', { state: 'phonesCatchAll' })
      .when('/:*url', { state: 'catchAll' });
}]);

Even though the last route would match the same urls as the routes defined above, due to the evaluation order it is possible to have this sort of pattern.

Build in Converters

Angular-Routing currently have two built-in converters defined, while the regex converter may cover all cases from a matching perspective, it may be useful to add your own converters to simplify the routes.

Number Converter

In the example above, we might want the phoneId only to match numbers. In order to do so we can use the new syntax with a converter instead.

angular.module('phonecat', ['dotjem.routing']).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider
      .when('/phones', { state: 'phones' })
      .when('/phones/{num:phoneId}', { state: 'phones.detail' })
      .otherwise( { redirectTo: '/phones' } );
}]);

Here we state that the phoneId should be converted using the num converter, which is a pre-registered converter and short for number.

Now if we go to /phones/fubar we won't match the route and get redirected to phones

Regular Expression Converter

Another pre-registered converter is the regex converter, which is short for Regular Expression, since this converter needs a regular expression to match the parameter against, we have to provide that as a parameter to the converter as demonstrated below:

angular.module('phonecat', ['dotjem.routing']).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider
      .when('/phones', { state: 'phones' })
      .when('/phones/{regex(^ph-[0-9]*$):phoneId}', { state: 'phones.detail' })
      .otherwise( { redirectTo: '/phones' } );
}]);

Here we say that the phoneId must match the regular expression ^ph-[0-9]*$ in order to activate the route. An alternative to the above is to use: {regex( { "exp" : "^ph-[0-9]*$", "flags": "i" } ):phoneId} which allows us to provide flags for the regular expression as well, and in this case make it case-insensitive

Custom Converters

It is also possible to create your own converters. To do this, use the convert function on the $routeProvider. Bellow is shown how the to pre-registered converters are added.

angular.module('phonecat', ['dotjem.routing']).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .convert('num', function () {
        return {
            parse: function (param) {
                var accepts = !isNaN(param);
                return {
                    accept: accepts,
                    value: accepts ? Number(param) : 0
                };
            },
            format: function (value) {
                if(isNaN(value)) {
                    throw new Error("...");
                }
                return value.toString();
            }
        };
    })
    .convert('regex', function (arg) {
        var exp, flags = '', regex;
        if(isObject(arg) && isDefined(arg.exp)) {
            exp = arg.exp;
            if(isDefined(arg.flags)) {
                flags = arg.flags;
            }
        } else if(isString(arg) && arg.length > 0) {
            exp = arg;
        } else {
            throw new Error("...");
        }
        regex = new RegExp(exp, flags);
        return {
            parse: function (param) {
                var accepts = regex.test(param);
                return {
                    accept: accepts,
                    value: accepts ? regex.exec(param) : null
                };
            },
            format: function (value) {
                var str = value.toString();
                var test = regex.test(str);
                if(!test) {
                    throw new Error("...");
                }
                return str;
            }
        };
    })
    .convert('', function () {
        return function (param) {
            return true;
        };
    });
}]);

In order to understand what is going on here, lets take a closer look at the above.

Interface

The convert function takes two parameters, the name of the converter and a function. The name is what is used in the route, so when we use {num:paramName} it refers the the converter registered as num in the above. The function is a factory function which is used to create an actual converter for each route parameter that uses it.

Factory Function

The factory function can optionally take a parameters, this will either be a string or a object that can be provided in a JSON format like so: { "stringProperty" : "string value", "numberProperty": 10 }.

Note: It uses the angular.fromJSON function, if it can't be converted as JSON it passes it on as a string instead.

The factory function then returns the actual converter, which can be a function that takes the parameter value as an argument, or it can be an object defining to properties: parse and format.

If we define the converter just as a function that is the same as the parse function, and the format will be replaced by a call to the objects toString method.

Parse Function

The parse funtion can either return a boolean, where true indicates that it accepts the parameter or false that it rejects it.

It can also return an object like both the num and regex converters parse functions does:

return {
    accept: accepts,
    value: accepts ? Number(param) : 0
};

This can be used to convert the parameter from a string to an object, in the above case we convert it to a Number.

Format Function

The format function should return a string that represents the value provided as it should be in the url.

It is recommended to keep converters as simple as possible and simply use an objects "toString" method, which is the same thing that is done if we don't define a formatter for the converter, however defining a formatter explicitly enables us to add validation so that we will only allow to construct urls that are valid.

Clone this wiki locally