How to implement path aliases in ui-router

angular-ui, angular-ui-router, angularjs

Solution

I. Doubled state mapping (reuse of controller, views)

NOTE: This is original answer, showing how to solve the issue with two states. Below is another approach, reacting on the comment by Geert

There is a plunker with working example. Say we have this two objects (on a server)

var articles = [
  {ID: 1, Title : 'The cool one', Content : 'The content of the cool one',},
  {ID: 2, Title : 'The poor one', Content : 'The content of the poor one',},
];

And we would like to use URL as

// by ID
../article/1
../article/2

// by Title
../article/The-cool-one
../article/The-poor-one

Then we can create this state definitions:

// the detail state with ID
.state('articles.detail', {
    url: "/{ID:[0-9]{1,8}}",
    templateUrl: 'article.tpl.html',
    resolve : {
      item : function(ArticleSvc, $stateParams) {

        return ArticleSvc.getById($stateParams.ID);
      },
    },
    controller:['$scope','$state','item',
      function ( $scope , $state , item){ 

        $scope.article = item;
    }],
  })

// the title state, expecting the Title to be passed
.state('articles.title', {
    url: "/{Title:[0-9a-zA-Z\-]*}",
    templateUrl: 'article.tpl.html',
    resolve : {
      item : function(ArticleSvc, $stateParams) {

        return ArticleSvc.getByTitle($stateParams.Title);
      },
    },
    controller:['$scope','$state','item',
      function ( $scope , $state , item){ 

        $scope.article = item;
    }],
  })

As we can see, the trick is that the Controller and the Template (templateUrl) are the same. We just ask the Service `ArticleSvc` to `getById()` or `getByTitle()`. Once resolved, we can work with the returned item...

The plunker with more details is here

II. Aliasing, based on native `UI-Router` functionality

Note: This extension reacts on Geert appropriate comment

So, there is a `UI-Router` built-in/native way for route aliasing. It is called

$urlRouterProvider - .when()

I created working plunker here. Firstly, we will need only one state defintion, but without any restrictions on ID.

.state('articles.detail', {
    //url: "/{ID:[0-9]{1,8}}",
    url: "/{ID}",

We also have to implement some mapper, converting title to id (the alias mapper). That would be new Article service method:

var getIdByTitle = function(title){
   // some how get the ID for a Title
   ...
}

And now the power of `$urlRouterProvider.when()`

 $urlRouterProvider.when(/article\/[a-zA-Z\-]+/,
  function($match, $state, ArticleSvc) {

    // get the Title 
    var title = $match.input.split('article/')[1];
    // get some promise resolving that title
    // converting it into ID
    var promiseId = ArticleSvc.getIdByTitle(title);

    promiseId.then(function(id){

      // once ID is recieved... we can go to the detail
      $state.go('articles.detail', { ID: id}, {location: false}); 
    })

    // essential part! this will instruct UI-Router, 
    // that we did it... no need to resolve state anymore
    return true;
  }
);

That's it. This simple implementation skips error, wrong title... handling. But that is expected to be implemented anyhow... Check it here in action

Problem

I'm trying to find a way to implement route aliases in Angular.js using `ui-router`. Let's say I have a state with the url `article/:articleId` and I want to have `/articles/some-article-title` redirected (internally) to `/article/76554` without changing the browser location. Could this be done with `ui-router`?

Original source