Blog

Angular reverse URL construction

29 Oct, 2015
Xebia Background Header Wave

Let’s discuss a problem you probably aren’t aware you are having in your web app:

Hardcoded URLs

These hardcoded, static URLs are simple at first, but the code is error prone and increases the app complexity. Instead what you should be doing is creating dynamic URLs by means of reverse URL construction. I have grown up using the Django web framework which extensively uses reverse routing and I love it. Let us look at how this simple system improves your coding and I’m sure you’ll love it too.

We’ll look at URL construction using an AngularJS example. Even if you don’t use Angular, the general idea behind reverse URLs should become apparent.

The stage

Say we have simple web application with a contact page, a list of users and user pages:
[code]
/contact -> contact.html
/users -> users.html
/user/foo -> user.html for user "foo"
[/code]
We will reference these routes directly in our page using the `<a>` tag and occasionally we might even require an URL inside JavaScript. A link to the contact page is easily created using: `<a href=”/blog/contact”>Contact us</a>. There are however a few drawbacks to this approach.
The first issue is that the URL is now defined in two places, the route configuration and the anchor tag. If you ever decide to change the URL to `contact-us`, all references should be updated as well. As the app grows so do all the duplicate references and you’ll end up changing a lot of them. Chances are that you will miss one and cause a bad link.
More problems arise as soon as the URL contains a dynamic element. Lets say the page behind `/users` list all users in our application as follows:
[code language=”html”]
<ul ng-repeat="user in users">
<li><a ng-href="/user/{{user.name}}"></a></li>
</ul>
[/code]
Because the URL is constructed using simple interpolation we can’t check whether the resulting URL is valid. Lets say the app router matches the URL on the pattern: `^/user/([a-z]+)$`. This would mean the user’s name may only consist of letters, for example “foo”. If the name contains an integer, say “foo2015”, the URL won’t be recognized by our router. So we allow bad URLs to be constructed.
So hardcoded URLs aren’t great, what is?

Reverse URL resolution

The solution has many names: URL construction, reverse URL resolution and reverse routing. The essence of this method is that every route is identifiable by a unique element. Reversing the URL is done by referencing the route and supplying parameters. The route itself is stored in a single location, the router. This route is used both for routing a request and constructing a URL.
We will add reverse routing to the standard Angular “$routeProvider”. A small project angular-reverse-url adds the reverse resolution capability. We can use a filter to build URLs by referencing either the route name or its controller name. In this example we will identify the route using the route name. But bear in mind that the controller approach would work just as well (given a route is uniquely defined by a controller).
Now let’s see how reverse URL construction is done. We will use the example setup as follows:
[code language=”javascript”]
function($routeProvider){
$routeProvider
.when(‘/contact’, {
template: ‘contact.html’,
name: ‘contact’
})
.when(‘/users’, {
template: ‘/users.html’,
name: ‘user-list’
})
.when(‘/user/:id/:name’, {
template: ‘/users.html’,
name: ‘user-detail’
})
}]);
[/code]

Constructing static URLs

Our web app needs needs a navigation bar. Inside our navigation we can link to the contact page using:
[code language=”html”]
<nav>
<ul>
<li><a ng-href="{{ ‘contact’ | reverseUrl }}">Contact us</a></li>
</ul>
</nav>
[/code]
The reverseUrl filter simply uses the ‘contact’ name to construct the URL from the route defined in the $routeProvider.

Parameter based URL generation

Creating plain URLs without any parameters is hardly interesting. The benefits of reverse URL resolution become apparent when we start introducing route arguments. We can list the user pages in our application using:
[code language=”html”]
<li ng-repeat="user in users">
<a href="{{ ‘user-detail’ | reverseUrl:{id: user.id, name: user.name} }}">{{user.name}}</a>
</li>
[/code]
Clean and simple!
There is not duplicate route defined and parameters are conveniently mapped by name. We explicitly map the parameters in this example, but we can also pass an object to the filter directly to greatly simplifying the statement. If you so prefer there is also the option to provide positional arguments:
[code language=”html”]
<a href="{{ ‘user-detail’ | reverseUrl:[user.id, user.name] }}">{{user.name}}</a>
[/code]

In code URL resolution

Occasionally the need arises to compose an URL directly in code. As `reverseUrl` is an Angular filter, we can simply call it directly inside our JavaScript:
[code language=”javascript”]
$filter(‘reverseUrl’)(‘user-detail’, { id: 1, name: 1 })
[/code]
This works just fine, though I would suggest wrapping the filter in a service if you often construct URLs in code.

Other solutions

The library presented here offers a very simple yet elegant way to do reverse URL resolution. It should be relatively easy to extend with extra functionality such as parameter type checking. There are also quite a lot of other libraries that offer reverse URL construction. We’ll look at the new Angular router and the popular ui-router.

Angular >= 1.5

The new Angular 1.5 router offers reverse URL resolution out of the box. If you’ve made the leap to the new component based routing, reverse resolution should be a breeze. There are some minor syntactical differences:
Configuration
[code language=”javascript”]
AppController.$routeConfig = [
{ path: ‘/user/:id/:name’, component: ‘user-detail’ }
];
[/code]
Template usage
[code language=”html”]
<a ng-link="user-detail({ id: 1, name:’foo’ })">Foo’s page</a>
[/code]

UI router

The wildly popular Angular ui-router also offers reverse routing. Chances are if you are using ui-router, you are using this reverse resolution of URLs already. If you are using ui-router and are still hardcoding URLs, switch today! Setup is easy:
Configuration
[code language=”javascript”]
$stateProvider
.state(‘user-detail’, {
URL: "/user/:id/:name",
controller: "UserDetailCtrl",
templateUrl: "views/user.html"
});
[/code]
Template usage
[code language=”html”]
<a ui-sref="user-detail({ id: 1, name: ‘foo’ })">Foo’s page</a>
[/code]

Stop hard coding URLs

With so many options, there really is no good reason to hard code URLs in your application. From now on, all URLs you create in any sizable project should be using a reverse URL resolution system.
Reverse URL construction is really simple, it keeps your routes in one place and standardizes URL generation in your app.
If you want to take a closer look at the Angular example code, see the full example: reverse-routing-Angular-example

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts