Pretty Url in AngularJS and Loopback. Drop the ‘#’

AngularJS routing system is great to create RESTful single-page applications, but it comes at the cost of accepting the # fragment in all your urls. There are several reasons you would like to drop this tiny character:

Good news, you can easily configure your application to go from:

myapp.com/#/my/angular/routes

To:

myapp.com/my/angular/routes

There are two things that need to be done:

  1. Configuring AngularJS to enable HTML5 mode
  2. Configuring your backend framework to redirect all non-REST and non-static-asset HTTP requests to the frontend index.html

Configuring your backend is necessary to tell your server to redirect the “/my/angular/routes” to the angular app, and avoid getting 404 errors. Here I will be using Loopback as an example of REST API framework, but it can be easily adapted to other frameworks like Spring or Symfony.

Step 1: Configuring AngularJS to enable HTML5 mode

This step will depend on which version of Angular you’re using.

Angular 1 with angular-route or angular-ui-router

When you set your angular application configuration, you simply have to use the $locationProvider module and set html5Mode to true.

angular.module('app')
  .config(config)

config.$inject = ['$locationProvider'];
function config($locationProvider) {
  $locationProvider.html5Mode(true);
}

To tell Angular what is the base path of your application, provide a base tag to your index.html


<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <base href="/">
  </head>

Angular 2

The equivalent of HTML5 mode in Angular 2 is to use the PathLocationStrategy as the router strategy.


import {ROUTER_PROVIDERS, APP_BASE_HREF} from 'angular2/router';

bootstrap(yourApp, [
  ROUTER_PROVIDERS, // includes binding to PathLocationStrategy
  provide(APP_BASE_HREF, {useValue: '/'})
]);

You can edit the APP_BASE_HREF to define your application base path

provide(APP_BASE_HREF, {useValue: '/my/app/path'})

Step 2: Configuring your backend

The above configuration will work on its own until you try to access an angular route directly with its url, because your web server won’t know he has to redirect this url to your angular application.

To fix that, you must configure your backend framework to redirect all your angular route urls to the index.html.

To do this, add a filter to the server.js file.

var path = require('path');

//List here the paths you do not want to be redirected to the angular application (scripts, stylesheets, templates, loopback REST API, ...)
var ignoredPaths = ['/vendor', '/css', '/js', '/views', '/api'];

app.all('/*', function(req, res, next) {
  //Redirecting to index only the requests that do not start with ignored paths
  if(!startsWith(req.url, ignoredPaths))
    res.sendFile('index.html', { root: path.resolve(__dirname, '..', 'client') });
  else
    next();
});

function startsWith(string, array) {
  for(i = 0; i < array.length; i++)
    if(string.startsWith(array[i]))
      return true;
  return false;
}

Now, all your requests that do not match the specified patterns will be taken care of by the angular routing system and not the Loopback one.

The only disadvantage is that you have to be careful when adding new assets or REST endpoints and be sure that their urls do not conflict with angular routes.

Conclusion

Congratulations! You have a fully functional angular application without any trace of # in urls!

What’s next? You could dive deeper into Angular state management features and implement basic route authorization in AngularJS.


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • Marcin Pilarczyk

    Thanks!!! Works for me!
    Perfect timing, since Thursday I was struggling with this issue. I couldn’t force my loopback to let me use html5mode. All the other solutions failed. Works with ui-router.

  • Georges Biaux

    Glad it helped you 😉

  • Marcin Pilarczyk

    Subpages, was my bad. My was to low in the below links to css.
    Now everything working great! Even as a part of a bigger portal, when using base href with correct directory name.
    Thanks again!

  • Georges Biaux

    You’re very welcome, thanks for the feedbacks :)

  • ggsouza

    Nice, this is what I was looking for.

    But I have one doubt. Do I have to keep the “files”: {“loopback#static”: { “params”: “$!../client” }} in my middleware.json?

  • ggsouza

    Great, I was just looking for this!

    But after studying a little more I have one suggestion for you.

    Instead of inserting this code in your server.js, I should insert it into a file inside the server/boot directory.
    As it’s a boot script, that’s the best place for it to be.

  • Itamar Serafim Silva

    Man, you saved my day!!!
    So helpful. Thank you!
    Warning: if you forget to include any folder in that array: “var ignoredPaths = [‘/vendor’, ‘/css’, ‘/js’, ‘/views’, ‘/api’];”
    you’ll get files not foun. I forget to inclde the “parts” and “images”, folders.