ui-router - nested routes

TL;DR

  • Repo for nested routes with ui-router
  • It can take some time to replace ng-route with ui-router, but ui-router can do everything that ngRoute does.
  • If your application is a single-page app, then ui-router should be a go-to solution.
  • a few related links on ui-router at the end

Recently I got a chance to replace ngRoute with ui-router in my current project. Even though ui-router is good at dealing with nested views, I set out to look for a nice general solution for nested routes, a better way to structure nested routes and views, and a better way to isolate controllers.

Let me explain in detail what I was looking for. Take a route authors/1/books/2 as an example. The view under the route would represent the book with id 2 of author with id 1. Exactly like a typical ios master-detail application, I want the view-transitions for such route to go like:

<view of a list of authors>
             | select author of id 1
<view authorId = 1 and books of authorId = 1>
             | select book of id 2
<view of book id 2 from authorId =1>

This kind of transition provides a few advantages:

  • isolating what the views are doing
  • providing information about the view through routes
  • easier for testing

However, the transitioning I am describing here has nothing to do with nested views. It goes from one view to another view, and is in fact only doing flat transitioning (as opposed to nested view transitioning). If I actually take the same route and represent it in ui-router with nested view, it would make more sense to view it like this:

<view of a list of authors>
             | select author of id 1
        <view authorId = 1 and books of authorId = 1>
                 | select book of id 2
               <view of book id 2 from authorId =1>
              </view of book id 2 from authorId =1>
        </view authorId = 1 and books of authorId = 1>
</view of a list of authors>

The reason why it should be a view under another view is that ui-router uses a tree to represent the relationship between the states, so in this example, the tree-like structure should look like:

authors state   
    |_author with authorId state that lists all the books 
        |_book state 

which maps to a single page nested views like this:

/authors

| list   |      | ui-view                       |
| of     |      |                               | 
| authors|      |                               |
|        |      |                               |
|        |      |                               |    

/authors/1
(ui-view gets replaced by author's info)

| list   |      | authorId = 1 author's info    |
| of     |      | | ui-view               |     | 
| authors|      | |                       |     |
|        |      | |                       |     |
|        |      | |                       |     |    

/authors/1/books
(ui-view gets replaced by the template of the list of books for authorId = 1)

| list   |      | authorId = 1 author's info    |
| of     |      | | list          |ui-view|     | 
| authors|      | | of books      |       |     |
|        |      | | from          |       |     |
|        |      | |  authorId = 1 |       |     |

/authors/1/books/2
(ui-view gets replaced by the book template view of bookId = 2)

| list   |      | authorId = 1 author's info    |
| of     |      | | list          |bookId=2  |  | 
| authors|      | | of books      |from      |  |
|        |      | | from          |authorId=1|  |
|        |      | |  authorId = 1 |          |  |

From here we can already see the benefits of using ui-router to structure a sinlge-page app. First of all the parent view does not get reloaded when the route is navigating to a child state, which means the child controller can access the scope variables from the parent state. And at the same time the only thing that gets reloaded is the child view and the child controller. Second, the routes can imply what the route is going to do, which is really nice, and by separating the functionalities in the app, the testings can essentially be done much easily. For example, one can also do the following to separate out the editting logic from the book controller:

/authors/1/books/2/edit

| list   |      | authorId = 1 author's info    |
| of     |      | | list          |edit view |  | 
| authors|      | | of books      |for       |  |
|        |      | | from          |bookId=2  |  |
|        |      | |  authorId = 1 |          |  |

The official website provides a nice similar working example , and ng-newsletter also gives a deep look into ui-router (Diving deep into the AngularUI Router).

Now back to the original flat-transitioning I was looking for. It turns out there are two ways to approach.

The first and the quickest way is to treat every view like a separate view/state. For example: authors/1/books/2 would be separated into four states that have no hierarchical relationship with each other, which means in app.js setup it would not use the keyword abstract at all, and would be similar to this:

$stateProvider
  .state('authors', {
    url: '/authors',
    templateUrl: ...
    controller: ...
  })
  .state('author', {
    url: '/authors/{authorId:[0-9]{1,4}}',
    templateUrl: ...
    controller: ...
  })
  .state('books', {
    url: '/authors/{authorId:[0-9]{1,4}}/books',
    templateUrl: ...
    controller: ...
  })
  .state('book', {
    url: '/authors/{authorId:[0-9]{1,4}}/book/{bookId:[0-9]{1,4}}',
    templateUrl: ...
    controller: ...
  })

And the second but the more complicated way is to follow the setup like this:

$stateProvider
    .state('authors', {
      abstract: true,
      url: '/authors',
      templateUrl: ...,
      controller: ...
    })
    .state('authors.index', {
      url: '',
      templateUrl: ...
    })
    .state('authors.detail', {
      abstract: true,
      url: '/{authorId:[0-9]{1,4}}',
      template: '<div ui-view></div>'
    })
    .state('authors.detail.index', {
      url: '',
      templateUrl: ...,
      controller: ... 
    })
    .state('authors.detail.books', {
      abstract: true,
      url: '/books',
      template: '<div ui-view></div>'
    })
    .state('authors.detail.books.index', {
      url: '',
      templateUrl: ...,
      controller: ...
    })
    .state('authors.detail.books.detail', {
      abstract: true,
      url: '/{bookId:[0-9]{1,4}}',
      template: '<div ui-view></div>'
    })
    .state('authors.detail.books.detail.index', {
      url: '',
      templateUrl: ...,
      controller: ...
    })

which I have a working example repo here, and it's built on top of the official example.

Let me know if you have any questions : )

Diving deep into the AngularUI Router

ui-router presentation

How to to force AngularJS resource resolution with ui-router

How to resolve application-wide resources centrally in AngularJS with ui-router

From Official Website:

Frequently Asked Questions

Official Example

API Reference

https://gist.github.com/auser/6590977

https://github.com/bevacqua/js/

comments powered by Disqus