ngRoute without hash (#) in HTML5 Mode

ngRoute without hash (#) in HTML5 Mode

Angular's ngRoute module makes use of the "hash bang" to do its routing. Originally, the hash symbol was used as a way to indicate a sort of bookmark that allowed the site creator to link within their own HTML page using an element's id as the target. Remember those old sites with a table of contents at the top, and you would click a section and it would jump partway down the page to the section you wanted? That used anchor tags with an href of the id attribute of another element somewhere on the same page.

Angular cleverly decided to use that same concept, but for view replacement instead, so that instead of jumping down the page it would just replace the content in the page with the content specified.

However, having the # in the middle of your URLs on your site isn't all that pleasant - it kind of breaks up the flow of your site a bit. It makes our urls look like:

  • http://localhost:8000/#/,
  • http://localhost:8000/#/todos, and
  • http://localhost:8000/#/profile

So Angular also created a way to accomplish routing by using the HTML5 History API instead of using the hash bang. There's a couple extra changes to be made to your application, but it's fairly straightforward. So let's do it!

Starting Point

We'll assuming you've already started an Angular application. Here's an example app.js you may have:

app.config(["$routeProvider", function ($routeProvider) {

    $routeProvider
        .when("/", {
            templateUrl: "components/home/home.html"
        })
        .when("/todos", {
            templateUrl: "components/todos/todos.html",
            controller: "TodoController"
        })
        .when("/profile", {
            templateUrl: "components/profile/profile.html",
            controller: "ProfileController"
        })
        .otherwise({
            redirectTo: "/"
        });
}]);

We're going to use the html5Mode method on the $locationProvider to configure our app to get rid of the # symbol. We can do this by simply injecting $locationProvider and adding $locationProvider.html5Mode(true) to our app.config:

app.js
app.config(["$routeProvider", "$locationProvider", function ($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);

    $routeProvider
        .when("/", {
            templateUrl: "components/home/home.html"
        })
        .when("/todos", {
            templateUrl: "components/todos/todos.html",
            controller: "TodoController"
        })
        .when("/profile", {
            templateUrl: "components/profile/profile.html",
            controller: "ProfileController"
        });
        // .otherwise({
        //     redirectTo: "/"
        // });
}]);

We've commented out the otherwise method on the $routeProvider, because it only works specifically with the hash bang method of routing.

It should also be noted that there are other options available on the html5Mode method, which you can see in the documentation.

The only other front-end code we need to add is to our index.html, inside the <head>:

index.html
...
<head>
    <base href="/">
    ...
<head>
...

This tells our app to expect that the base of all our URLs is a "/".

We've also been required to put the # in all of our hrefs up until now, but we can go through and remove those from anywhere on our site. So instead of <a href="#/login">Login</a>, it should be <a href="/login">Login</a> instead. Make sure to change those everywhere in the code or you'll run into issues.

With those simple additions, we now have our urls looking more like:

  • http://localhost:8000,
  • http://localhost:8000/todos, and
  • http://localhost:8000/profile.

Server-side updates

The problem with only doing this on the client-side is what will happen if the user tries to type a path into the url bar and they mistype something or type something that isn't already handled by routing, like if they tried to get to http://localhost:8000/todos-are-great:

Once Angular's ngRoute realizes it doesn't have a route set up to handle the request the user typed in ("todos-are-great"), the browser will attempt a GET request to Express (or nginx, or ASP.net, or PHP, or whatever codebase your server is running), and the server will return a 404, saying it doesn't know what to do with that request.

In order to mimic the otherwise method we were using before the change to HTML5 Mode, we need to add a catchall to our Express application so that if it doesn't know what to do with the request, it will send the index.html file to the client, essentially re-routing the person to the home page. In server.js:

app.all('/*', function(req, res, next) {
    res.sendFile('index.html', { root: __dirname + "/../public" });
});

Now, instead of sending a 404 error screen, it will simply send the user to the index page of the site.

Conclusion

You can decide for yourself if it's worth removing the # from your URLs. Fortunately Angular made it relatively easy to do so if we wanted.

One final note: if you're not using Express for your server, but you still want to use HTML5 mode on an Angular site you're wanting to deploy, you can also use some code in Apache or Nginx to accomplish the same thing. Check out this site for a few examples on how to accomplish this.